本文最后更新于:2023年8月24日 晚上
setState(状态更新) 在类组件中,setState用于更新state中的数据,有以下两种写法:
对象式:setState(stateChange, [callback])
stateChange为一个对象,更新后的数据
callback为render调用后执行的回调函数
函数式setState(updater, [callback])
updater为一个函数,返回stateChange作为更新后的数据
update可以接收到state和props
callback同上
因为react中setState为同步函数,但是由setState引起的修改操作是异步的,如果要获取最新的状态数据,需要在回调函数中读取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 setCount = () => { const { count } = this .state this .setState ({ count : count + 1 }, () => { console .log ('状态更新完毕' ) }) this .setState ((state: any , props ) => { console .log ('接收到的参数' , props) return { count : state.count + 1 } }, () => { console .log ('状态更新完毕' ) }) }
手动简化函数式写法:
1 2 3 4 this .setState (({ count }: { count: number } ) => ({ count : count + 1 }), () => { console .log ('状态更新完毕' ) })
对象式写法其实是函数式写法的语法糖,推荐使用条件:
当修改后的状态不需要在state基础上更新时,使用对象式写法
当需要在state基础上更新时,使用函数式
lazyload(组件懒加载) lazyload一般来说是对于路由组件使用的,用法如下:
不使用懒加载的路由 1 2 3 4 5 6 7 8 9 import MenuLink from './components/MenuLink' import My from './components/My' ... <Routes > <Route path ="/" element ={ <Navigate to ="/main" /> } /> <Route path ="/main" element ={ <MenuLink /> } /> <Route path ="/my" element ={ <My /> } /> </Routes >
使用懒加载
引入lazy,用法类似vue3的defineAsyncComponent
将路由标签使用Suspense标签包裹,并指定加载过程显示组件
1 2 3 4 5 6 7 8 9 10 11 12 13 import React , { Component , lazy, Suspense } from 'react' const MenuLink = lazy (() => import ('./components/MenuLink' ))const My = lazy (() => import ('./components/My' )) ... <Suspense fallback={<h1 > Loading ...</h1 > }> <Routes > <Route path ="/" element ={ <Navigate to ="/main" /> } /> <Route path ="/main" element ={ <MenuLink /> } /> <Route path ="/my" element ={ <My /> } /> </Routes > </Suspense >
fallback可以直接指定一个组件,像这样 <Suspense fallback={<Loader/>}>,这里的Loader组件必须先引入,不可懒加载
lazy不可放入函数组件中,会导致浏览器一直处于加载状态
Hooks useState 因为函数式组件中没有state状态,所以需要引入useState创建一个状态管理,如下:
1 2 const [countNum, setCountNum]: [countNum : number , setCountNum : (num: number ) => void ] = useState (0 )
const [参数名,更新函数名] = useState(初始值)
setCountNum有两个用法:
直接传递更新后的数据
传递一个函数,回调参数为count,返回的是更新后的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import { useState } from 'react' type ISetCount <T> = (callback: ((count: T) => T) | number ) => void const Index = (props: any ) => { const [count, setCount]: [count : number , setCount : ISetCount <number >] = useState (0 ) const addCount = ( ) => { setCount (count + 1 ) setCount (count => count + 1 ) } return ( <> <h1 > 当前值为:{count}</h1 > <button onClick ={addCount} > 点击增加</button > </> ) }
如果使用解构的方式,推荐第一种,因为第一个参数就是count
useEffect 在React更新DOM后调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import { useEffect, useState } from 'react' const Count = ( ) => { const [num]: [num : number , setNum : (num: number ) => void ] = useState (0 ) const [age, setAge]: [num : number , setNum : (num: number ) => void ] = useState (0 ) useEffect (() => { console .log ('Effect:渲染完毕' , num) return () => { console .log ('Effect:销毁完毕' , num) } }, [num, age]) return ( <div > <button onClick ={() => setAge(age + 1)}>修改</button > </div > ) }export default Count
执行顺序,首次渲染时,渲染完毕先执行,然后执行一次销毁完毕,后再次执行渲染完毕
后续更新渲染时:销毁完毕(可以获取到更新前的state) -> 渲染完毕
useEffect 第二个参数是一个数组,传递的值作为检测值,在后续更新渲染时,检测值发生变化才会触发 useEffect 中的函数,如这里如果比较值只有一个num,则除了第一次渲染会触发函数,后续点击都不会触发,如果是空数组,则只会在第一次渲染render的时候调用一次
自动累加器案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import { useEffect, useState } from 'react' import root from '../../main' type ISetCount <T> = (callback: ((count: T) => T) | number ) => void const Index = (props: any ) => { const [count, setCount]: [count : number , setCount : ISetCount <number >] = useState (0 ) let timer : number useEffect (() => { timer = setInterval (() => { setCount (count => count + 1 ) }, 500 ) return () => { clearInterval (timer) } }, []) const unNode = ( ) => { root.unmount () } return ( <> <h1 > 当前值为:{count}</h1 > <button onClick ={unNode} > 卸载组件</button > </> ) }
useRef 1 2 3 4 5 6 7 8 const inputRef = useRef () as MutableRefObject <HTMLInputElement >console .log (inputRef.current ?.value )return ( <> <h1 > 当前值为:{count}</h1 > <input type ="text" ref ={inputRef} /> </> )
使用?.就算value属性为undefined也不会报错
Fragment Fragment标签作用就和vue中的template差不多,当你不想要有一个父标签时可以使用,只支持接收一个参数key方便遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const list = [...Array (100 ).keys ()].map (v => ({ id : v, key : 'F_SAN' + v.toString () }))return ( <> <h1 > 当前值为:{count}</h1 > <button onClick ={unNode} > 卸载组件</button > { list.map(v => ( <Fragment key ={v.id} > <p > {v.key}</p > </Fragment > )) } </> )
如果这里是div,那么每个p标签都会套上一个div
[...Array(100).keys()]用于生成指定长度数组,内容为0-99
createContext(上下文) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 import { createContext } from 'react' import { nanoid } from 'nanoid' const { Provider , Consumer } = createContext ('默认值' )const C = ( ) => { return ( <div style ={{ background: '#EFEFEF ', padding: '20px ' }}> <Consumer > { value => { const data = JSON.parse(value) return ( <table > <thead > <tr > <th > 姓名</th > <th > 年龄</th > </tr > </thead > <tbody > { data.map((v: any) => ( <tr key ={v.id} > <td > {v.name}</td > <td > {v.age}</td > </tr > )) } </tbody > </table > ) } } </Consumer > </div > ) }const B = ( ) => { return ( <div style ={{ background: '#D1D1D1 ', padding: '20px ' }}> <h2 > 这是B组件</h2 > <C /> </div > ) }const My = ( ) => { const tableData = [ { id : nanoid (), name : 'F_SAN' , age : 20 }, { id : nanoid (), name : 'F_SAN1' , age : 21 }, { id : nanoid (), name : 'F_SAN2' , age : 22 } ] return ( <div style ={{ background: '#C0A080 ', padding: '20px ' }}> <h1 > 这是A组件</h1 > <div > A组件的name为</div > <Provider value ={JSON.stringify(tableData)} > <B /> </Provider > </div > ) }export default My
可以对于子组件对及其以下的全部孙组件的数据传递
注意用法:
Provider标签的value属性表示传递数据,也可以为对象或数组,推荐对象写法,解构时无需考虑顺序
向下传递的所有子组件都可以使用Consumer标签来接收数据,内容是一个函数,value即使传递的数据
useContext(使用上下文) 在createContext中,传递过去的参数,使用Consumer标签接受较为繁琐,所以就有了useContext
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 import { createContext, useContext } from 'react' import { nanoid } from 'nanoid' const dataContext = createContext ('默认值' )const C = ( ) => { const value = useContext (dataContext) const data = JSON .parse (value) return ( <div style ={{ background: '#EFEFEF ', padding: '20px ' }}> <table > <thead > <tr > <th > 姓名</th > <th > 年龄</th > </tr > </thead > <tbody > { data.map((v: any) => ( <tr key ={v.id} > <td > {v.name}</td > <td > {v.age}</td > </tr > )) } </tbody > </table > </div > ) }const B = ( ) => { return ( <div style ={{ background: '#D1D1D1 ', padding: '20px ' }}> <h2 > 这是B组件</h2 > <C /> </div > ) }const My = ( ) => { const tableData = [ { id : nanoid (), name : 'F_SAN' , age : 20 }, { id : nanoid (), name : 'F_SAN1' , age : 21 }, { id : nanoid (), name : 'F_SAN2' , age : 22 } ] return ( <div style ={{ background: '#C0A080 ', padding: '20px ' }}> <h1 > 这是A组件</h1 > <div > A组件的name为</div > <Provider value ={JSON.stringify(tableData)} > <B /> </Provider > </div > ) }export default My
直接将创建出来的上下文对象交给useContext,就可以获取到其中的值
优化子父组件渲染 状态更新后的重新渲染会非常影响性能,特别是子组件数量多的情况下
类式组件优化 方法一:自己修改钩子 1 2 3 4 shouldComponentUpdate (nextProps : Readonly <{}>, nextState : Readonly <{}>, nextContext : any ): boolean { return this .state .name !== nextState.name }
方法二:使用PureComponent 1 2 class Index extends PureComponent { }
将Component替换为PureComponent,react将自行浅判断
函数式组件优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import React , { useState, useEffect, memo } from 'react' const IndexChildren = (props: any ) => { useEffect (() => { console .log ('children render' ) }) return ( <> <h1 > 这是子组件</h1 > <div > 从父组件传递的姓名为:{props.name}</div > </> ) }const Index = ( ) => { const [count, setCount] = useState (0 ) const [name, setName] = useState ('' ) useEffect (() => { console .log ('parent render' ) }) return ( <> 这是父组件当前更新次数为:{count},当前姓名为:{name} <hr /> <button onClick ={() => setCount(count => count + 1)}>更新状态</button > <button onClick ={() => setName('FSAN')}>更新传递的姓名</button > <IndexChildren name ={name} /> <hr /> </> ) }export default memo (Index )
memo默认只会浅比较props,如果需要定制比较,可以给第二个参数传入函数
1 2 3 4 5 6 7 8 9 10 11 function MyComponent (props ) { }function areEqual (prevProps, nextProps ) { }export default React .memo (MyComponent , areEqual);
注意:和shouldComponentUpdate不同,如果props相同则应返回true,否则返回false。这点二者正好相反。
子父组件两种构成方式 使用props 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import { memo } from 'react' const Parent = (props: any ) => { return ( <> <h1 > 这是Parent组件</h1 > <div > 下面是子组件 <hr /> {props.children} </div > </> ) }const Children = ( ) => { return ( <> <h1 > 这是子组件</h1 > </> ) }const Index = ( ) => { return ( <Parent > <Children /> </Parent > ) }export default memo (Index )
也可以这样直接使用属性传递:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import { memo } from 'react' const Parent = (props: any ) => { return ( <> <h1 > 这是Parent组件</h1 > <div > 下面是子组件 <hr /> {props.render()} </div > </> ) }const Children = (props: any ) => { return ( <> <h1 > 这是子组件</h1 > </> ) }const Index = ( ) => { return ( <Parent render ={Children} /> ) }export default memo (Index )
使用属性渲染的时候,就可以父组件向子组件传递参数了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import { memo } from 'react' const Parent = (props: any ) => { return ( <> <h1 > 这是Parent组件</h1 > <div > 下面是子组件 <hr /> {props.render({ name: 'FSAN' })} </div > </> ) }const Children = (props: any ) => { return ( <> <h1 > 这是子组件</h1 > <div > 从父组件接收到的参数为:{props.name}</div > </> ) }const Index = ( ) => { return ( <Parent render ={Children} /> ) }export default memo (Index )
直接子组件放入父组件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import { memo } from 'react' const Parent = (props: any ) => { return ( <> <h1 > 这是Parent组件</h1 > <div > 下面是子组件 <hr /> <Children /> </div > </> ) }const Children = ( ) => { return ( <> <h1 > 这是子组件</h1 > </> ) }export default memo (Parent )
甚至可以这样:
1 2 3 4 5 <div> 下面是子组件 <hr /> {Children ()} </div>
传递参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import { memo } from 'react' const Parent = (props: any ) => { return ( <> <h1 > 这是Parent组件</h1 > <div > 下面是子组件 <hr /> {Children({ name: 'FSAN' })} </div > </> ) }const Children = (props:any ) => { return ( <> <h1 > 这是子组件</h1 > <div > 从父组件接收到的参数为:{props.name}</div > </> ) }export default memo (Parent )
错误处理 函数式组件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 const Index = ( ) => { const data = '' let render try { render = ( <> { data.map(v => (<p key ={v.key} > 姓名为:{v.name}; 年龄为:{v.age}</p > )) } </> ) } catch (error) { console .log (error) render = ( <h1 > 发生未知错误!</h1 > ) } return render }export default Index
类式组件 类式组件可以使用componentDidCatch生命周期,它会收集错误,可以返回state对象,最后判断渲染即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class ErrorBoundary extends React.Component { state = { hasError : false } static getDerivedStateFromError (error ) { return { hasError : true }; } componentDidCatch (error, errorInfo ) { logErrorToMyService (error, errorInfo); } render ( ) { if (this .state .hasError ) { return <h1 > 发生未知错误.</h1 > ; } return this .props .children ; } }
以上是封装好的代码,使用如下:
1 2 3 <ErrorBoundary > <Header /> </ErrorBoundary >
组件通信方式总结
props:
普通属性
渲染函数
消息订阅-发布
pubs-sub,event等
集中式管理
redux,dva等
conText(开发用的少,封装用的多)
生产者-消费者模式