React快速入门
全部代码:https://github.com/ziyifast/front-demo
- React特点:
- 声明式设计:声明范式
- 高效:使用VDOM,减少DOM的交互
- 灵活:与已知的库或框架完好配合
- JSX:一种独立的语言,试图解决很多JS的缺陷,ES6包含了几乎所有JSX的特性
- 组件:代码复用
- 单向响应数据流:比双向绑定更简单,更快。
1 核心篇
准备:创建项目
我这里使用VSCode创建项目,在终端执行下面命令
# 通过脚手架创建项目npx create-react-app 1-react-core-demo# npx create-react-app@latest # 创建项目,自选功能# 进入项目cd 1-react-core-demo# 启动项目npm start
App.js为React默认主页面
1.1 核心概念及语法
①组件:函数式组件
React分为函数式组件和类组件,官方推荐使用函数式组件,因为类组件编写起来较复杂。我这里主要演示类组件用法。
App.js:
解释:我下面定义了一个函数式组件App,并且将其return暴露
- 注意点:
- React使用语法为JSX(JS+HTML),只能有一个根标签=》或者Fragment。所以我在多个标签外用标签包裹了一层
import './App.css';function App() {return (// JSX(JavaScript+HTML)规定只能有一个根标签,如果需要添加标签// 1. 可以在外面嵌套一层// 2. 使用空标签// 3. 如果有id,可以使用Fragment<><div className="App"><div>let's do it</div></div><div>who want to try " /></div></>);}export default App;
②插值
解释:类似引用,我先定义一个值,然后通过{变量名}引用值
App.js
function App() {// 插值(先定义变量,然后通过花括号引用值):{} let content = "hello world"return (<div>{content}</div>);}export default App;
③数据渲染
数据渲染主要有:
- 条件渲染(if)
- 列表渲染(array)=> map遍历
import './App.css';function App() {// 数据渲染:// 1. 条件渲染// 2. 列表渲染//--------------1. 条件渲染-------------// let content = "hello world"// let flag = false// if (flag) {// //注意:此处不用加引号// content = 老侄,flag为true// } else {// content = 大舅,flag为false// }// return (// // 嘿 man// {content}// // );//--------------2. 列表渲染-------------let userList = [{id:1,name:"徐杰"},{id:2,name:"郭艾伦"},{id:3,name:"易建联"}]// 通过map来遍历const listContent = userList.map((item,index)=>{return <li key={index}>{item.name}</li>})return (<div className="App"><div>{listContent}</div></div>);}export default App;
④事件处理
- 定义function
- 通过{funciton_name}引用
- 标签内绑定事件,onClick等
import './App.css';function App() {// 事件处理:无法实现响应式效果,需要结合useState来实现function handleClick(e){console.log("浏览器传入的值:"+e.target.innerText);}return (<div className="App"><button onClick={handleClick}>Click me</button></div>);}export default App;
⑤状态:useState
通过useState实现响应式效果
- const [content, setContent] = useState(“Hello World”)
- useState会返回一个数组,第一个值为保存的值,第二个值是一个func,用于设置新的值
import './App.css';import {useState} from 'react'; //导入钩子函数function App() {// 状态处理:useState实现,本质是将之前的值全部替换,因此setContent需要带上之前的值// 1. 字符串// 2. 对象// 3. 数组//------------------1. 字符串--------------------// const [content, setContent] = useState("Hello World")// function handleClick(){// setContent("中国男篮")// }// return (// // {content}// // // );//------------------2. 对象--------------------// const [content, setContent] = useState({// name:"Jackson",// age:20// })// function handleClick(){// setContent({// ...content, //...content是将对象之前的内容全部拿过来(useState的set操作是全部替换,如果这里只赋值age,那么会丢失name)// age:18 //如果之前了age字段,那么我们这里再赋值就会替换之前的值// })// }// return (// // {content.name}// {content.age}// // // )//------------------3. 数组--------------------const [content, setContent] = useState([{id:1, name:"库里"},{id:2, name:"欧文"},{id:3, name:"姚明"},])const listData = content.map(item => (<li key={item.id}>{item.id}-{item.name}</li>))//点击之后在默认添加元素function handleClick(){setContent([...content,{id:4, name:"詹姆斯"}])}//点击之后通过filter删除元素function handleClick2(){setContent(content.filter(item => item.id !== 2))}return (<div><ul>{listData}</ul><div>what?</div><button onClick={handleClick}>点我新增</button><button onClick={handleClick2}>点我删除2号</button></div>)}export default App;
1.2 组件通信与插槽
组件:
- React DOM组件
- React 自定义组件
①React DOM组件:如img标签
import './App.css';import image from './logo.svg'function App(){// 操作React DOM组件(原生html的一些标签)const imgData = {className: 'small',style: {width: '100px',height: '100px'}}return (<div><img src = {image}alt = "悬浮文字xxx"{...imgData}/></div>)}export default App;
②自定义组件:function xxx
前面我们介绍了react主要有函数式组件与类组件,下面我将演示如何自定义函数式组件
// 自定义组件// 父组件向子组件传值:通过props,如:{text,active}function Detail({text,active}){return (<><p>{text}</p><p>状态:{active ? '显示中' : '已经隐藏'}</p></>)}function Article({title, content}) {return (<div><div>{title}</div><Detail {...content}/></div>)}export default function App(){const articleData = {title: '文章标题',content:{'text': '文章内容','active': true}}return (<><Article{...articleData}/></>)}
③实现插槽效果:children
- JSX实现Vue插槽效果=》 children(简单版props,children被预先定义)
- 运行下面的代码可以知道我把
- 等标签及内容页传给了List组件,实现了类似Vue插槽效果
App.js
// JSX:实现类似Vue插槽效果:通过children传递(children这个属性是固定的)function List({children}){return (<ul>{children}</ul>)}export default function App(){return (<><List><span>第一列</span><li>列表项1</li><li>列表项2</li><li>列表项3</li></List><List><span>第二列</span><li>列表项a</li><li>列表项b</li><li>列表项c</li></List></>)}
④子组件向父组件传值
- 子组件向父组件传值=》自定义事件=》子组件触发事件来对应修改父组件(如果组件的值是可选的,那么需要在参数那赋值一个默认值,否则会有语法错误)
- 同级组件传值=》常用做法:通过父组件做一个中转
- 多级组件传值=》context=>createContext、useContext,通过Provider设置值
import {useState} from 'react' //注意手动:引入时,不要忘记{}// 子组件向父组件传值=》自定义事件=》子组件触发事件来对应修改父组件function Detail(){const [status, setStatus] = useState(false)function changeStatus(){setStatus(!status)}return (<div><button onClick={changeStatus}>点我有惊喜</button><p style={{display:status?'block':'none'}}>大聪明~</p></div>)}export default function App(){return (<div><Detail /></div>)}
1.3 React Hooks
下面我将介绍React常用的几个钩子函数
- react 函数组件的钩子函数(hook) hook函数是一个特殊的函数,目的是让函数组件也有类组件的特性. 类组件中可能一个周期函数中有多个业务逻辑代码,不利于维护. 类组件的学习成本相对较高,需要掌握es6的语法.
①useState:定义和更新组件的局部状态。
import {useState} from 'react'export default function App(){const [score, setScore] = useState(0)function handleClick(){setScore(score+1)}return (<div><p>月薪:{score}K</p><button onClick={handleClick}>点我升职加薪</button></div>)}
②useContext:可以避免props层层传递的情况,方便共享数据。
import { createContext, useContext } from 'react';export function Section({ children }) {const level = useContext(LevelContext)return (<section className='section'><LevelContext.Provider value={level + 1}>{children}</LevelContext.Provider></section>)}const LevelContext = createContext(0); // 默认值为0export function Heading({ children }) {const level = useContext(LevelContext);switch (level) {case 1:return <h1>{children}</h1>case 2:return <h2>{children}</h2>case 3:return <h3>{children}</h3>case 4:return <h4>{children}</h4>case 5:return <h5>{children}</h5>default:throw new Error('Invalid heading level')}}export default function App() {return (<div><Section><Heading>主标题</Heading><Section><Heading>副标题</Heading><Heading>副标题</Heading><Heading>副标题</Heading><Section><Heading>子标题</Heading><Heading>子标题</Heading><Heading>子标题</Heading><Section><Heading>子子标题</Heading><Heading>子子标题</Heading><Heading>子子标题</Heading></Section></Section></Section></Section></div>)}
③useReducer:结合useReducer和dispatch可以实现类似Redux的状态管理。
import {useReducer} from 'react'export default function App(){function reducer(state, action){switch(action.type){case 'up':return state + 1case 'down':return state -1}}const cnts = 0//计数器const [state, dispatch] = useReducer(reducer, cnts)const handleIncre = () => dispatch({type:'up'})const handleDecre = () => dispatch({type:'down'})return (<div style={{padding:20}}><button onClick={handleDecre}>-</button><span> {state} </span><button onClick={handleIncre}>+</button></div>)}
④useRef:引用之前的值、之前的标签、之前的组件
import {useRef,useState} from 'react'export default function App(){const [count, setCount] = useState(0)const prevCount = useRef()function handleClick(){//记录更新之前的count值prevCount.current = countsetCount(count + 1)}return (<div style={{padding:20}}><p>最新的count:{count}</p><p>上一次的count:{prevCount.current}</p><button onClick={handleClick}>增加</button></div>)}
⑤useEffect:主键加载时,可以设置一些“副作用”
后端的同学可以结合监视者设计模式来理解
import {useEffect,useState} from 'react'export default function App(){const [count, setCount] = useState(0)//监听count值,当变化时,执行一些操作,[]里表示监听那些数据useEffect(()=>{console.log('count变化了...do somethings');},[count])function handleClick(){setCount(count + 1)}return (<div style={{padding:20}}><p>count:{count}</p><button onClick={handleClick}>增加</button></div>)}
⑥useMemo:用于数据缓存,父组件的重新渲染会导致子组件的代码也重新执行,这时我们可以通过useMemo来缓存数据,通过[value]来指定当value值变化时,才会重新执行子组件的某个逻辑
import { useMemo, useState } from 'react'/*useMemo:缓存值,当监听的值有变化时,才会重新执行逻辑-- 本案例只有当输入框中的数字有变化时,才会重新执行复杂逻辑*/function DoSomeDiffcult({ value }) {//缓存第一次的result值,后续只有当传入函数的value值变化时,才会重新执行逻辑,否则直接从缓存中取值返回const result = useMemo(() => {let result = 0console.log('执行系列复杂计算....');result =value * 24124142return result;}, [value])return (<div><p>输入内容:{value}</p><p>经过复杂处理后的数据:{result}</p></div>);}function App() {const [count, setCount] = useState(0)const [inputValue, setInputValue] = useState(3)return (<div style={{ padding: 20 }}><p>count的值为:{count}</p><button onClick={() => setCount(count + 1)}>点击更新</button><br/><br/><input type='number'value={inputValue}onChange={e => setInputValue(parseInt(e.target.value))}></input><DoSomeDiffcult value={inputValue} /></div>)}export default App;
⑦useCallback:用于函数缓存,memo记忆子组件+useCallback,保证传入的函数是同一个,从而控制子组件不重新渲染
import { memo, useState, useCallback } from 'react'/*useCallback: 缓存函数-- 通过useCallback保证传入的是同一个handleClick,防止每次父组件更新,子组件都被迫更新-- 当触发子组件的click事件时,才会更新*/const Button = memo(function({onClick}){console.log('button 被渲染了');return <button onClick={onClick}>子组件</button>})function App() {const [count, setCount] = useState(0)const handleClick = useCallback(() => {console.log("点击按钮");},[]);return (<div style={{ padding: 20 }}><p>count的值为:{count}</p><button onClick={() => setCount(count + 1)}>点击更新</button><br/><br/><Button onClick={handleClick}></Button></div>)}export default App;
2 实战篇
了解了React的核心概念之后,下面进入demo环节。通过我们学习的知识实现一个todoList代办列表。
全部代码
:https://github.com/ziyifast/front-demo/tree/main/2-my-todo-list
最终项目结构:
2.1 创建项目
# 创建项目,然后根据提示设置自己所需要的内容npx create-next-app@latest# 创建完成之后,cd 进入我们的项目,执行npm run dev启动项目npm run dev
2.2 分析&创建types&components
我们要实现一个
代办事项
:
- types.ts:一个代办项包括它的内容text,它的状态completed,以及它的id。
- components:自定义所需组件
- AddTodo.tsx:添加代办模块
- TodoFilter.tsx:过滤模块(全部、已完成、未完成)
- TodoItem.tsx:单个代办项(包含text、删除按钮、已完成按钮)
- TodoList.tsx:页面主体(展示代办项)
- pages.tsx:组装组件,将自定义组件放入主页面
①types.ts
// 定义代办项export interface Todo {id: number;text: string;completed: boolean;}
②components:定义页面所需组件
创建components文件夹,在该文件夹下创建页面所需组件
- AddTodo.tsx:添加代办模块
- TodoFilter.tsx:过滤模块(全部、已完成、未完成)
- TodoItem.tsx:单个代办项(包含text、删除按钮、已完成按钮)
- TodoList.tsx:页面主体(展示代办项)
1. AddTodo.tsx:添加代办模块
import React, { useState } from "react"interface AddTodoProps {addTodo: (text: string) => void}export function AddTodo({addTodo}: AddTodoProps){const [text, setText] = useState('')//处理提交请求const handleSubmit = (e: React.FormEvent<HTMLFormElement>) =>{//阻止表单默认行为e.preventDefault()addTodo(text)//提交之后将输入框置空setText('')}return (<form onSubmit={handleSubmit}><input aria-label="新建代办"type="text" value={text} onChange={(e) => setText(e.target.value)}/><button type="submit">新建代办</button></form>)}
2. TodoFilter.tsx:过滤模块(全部、已完成、未完成)
export default function TodoFilter({setFilter}:any){return (<div><button onClick={()=>setFilter('all')}>All</button><button onClick={()=>setFilter('completed')}>Completed</button><button onClick={() => setFilter('active')}>active</button></div>)}
3. TodoItem.tsx:单个代办项(包含text、删除按钮、已完成按钮)
export function TodoItem({todo, toggleTodo, deleteTodo}:any) {return (<li style={{textDecoration: todo.completed " />'line-through' : 'none'}}>{todo.text}<button onClick={() => toggleTodo(todo.id)}>切换</button><button onClick={() => deleteTodo(todo.id)}>删除</button></li>)}
4. TodoList.tsx:页面主体(展示代办项)
import { Todo } from "@/types"import { TodoItem } from "./TodoItem";interface TodoListProps {todos: Array<Todo>;deleteTodo: (id: number) => void;toggleTodo: (id: number) => void;}export default function TodoList({ todos, deleteTodo, toggleTodo }: TodoListProps) {return (<ul>{todos.map(todo => (<TodoItem key={todo.id} todo={todo} deleteTodo={deleteTodo} toggleTodo={toggleTodo} />))}</ul>)}
③page.tsx
"use client";import Image from "next/image";import styles from "./page.module.css";import TodoFilter from "@/components/TodoFilter";import TodoList from "@/components/TodoList";import { AddTodo } from "@/components/AddTodo";import { useState } from "react";import { Todo } from "@/types";export default function Home() {//初始代办事项为空const [todos, setTodos] = useState<Todo[]>([])const [filter, setFilter] = useState('all')const addTodo = (text: string) => {const newTodoItem = {id: Date.now(),text,completed: false}setTodos([...todos, newTodoItem])}const deleteTodo = (id: number) => {setTodos(todos.filter(todo => todo.id !== id))}const toggleTodo = (id: number) => {setTodos(todos.map(todo => {if (todo.id === id) {todo.completed = !todo.completed}return todo}))}const getFilterTodo = () => {switch (filter) {case 'completed':return todos.filter(todo => todo.completed)case 'active':return todos.filter(todo => !todo.completed)default:return todos}}return (<div><h1>my-todo-list</h1><AddTodo addTodo={addTodo}></AddTodo><TodoList todos={getFilterTodo()} deleteTodo={deleteTodo} toggleTodo={toggleTodo}></TodoList><TodoFilter setFilter={setFilter}></TodoFilter></div>);}
④效果
页面效果很简陋,主要给大家展示用法,后续大家可以引入第三方样式或者自定义样式来美化
# 进入并运行项目cd 2-my-todo-list/npm run dev
页面效果: