当前位置: 首页 > 科技观察

ReactHooks入门完整指南

时间:2023-03-12 19:40:53 科技观察

介绍Hooks是函数组件特有的。无需编写类即可使用状态和其他React功能。只能在功能组件的顶级范围内使用;只能用在函数组件或其他Hooks中。使用hooks时,必须保证所有的Hooks都必须执行。必须按顺序进行。ESlint使用了Hooks的一些特性,必须遵循一定的规则。React官方提供了一个ESlint插件,专门用来检查Hooks是否使用正确。安装插件:npminstalleslint-plugin-react-hooks--save-dev在ESLint配置文件中添加两条规则:rules-of-hooks和exhaustive-deps。{"plugins":[//..."react-hooks"],"rules":{//...//查看Hooks的使用规则"react-hooks/rules-of-hooks":"error",//检查依赖声明"react-hooks/exhaustive-deps":"warn"}}useStateimportReact,{userState}from'react'functionExample(){//声明一个名为count的状态变量,初始值为0//你可以使用setCount来改变这个计数const[count,setCount]=useState(0)return(

你点击了{count}次

setCount(count+1)}>点我
);}useState的入参也可以是函数。当输入参数是一个函数时,这个函数只会在组件初始渲染时执行。const[count,setCount]=useState(()=>{constinitialCount=someExpensiveComputation(props)returninitialCount})setState也可以接收一个函数作为参数:setSomeState(prevState=>{})。//常用写法consthandleIncrement=useCallback(()=>setCount(count+1),[count])//下面的性能更好//不会每次count变化都用一个新的。//因此,接收这个函数的组件的props被认为是不变的,避免了可能的性能问题consthandleIncrement=useCallback(()=>setCount(q=>q+1),[])useEffectuseEffect会在每个DOM之后执行rendering,不阻塞页面渲染。页面更新后执行。即:在每个组件渲染后,确定依赖关系并执行它们。它还具有componentDidMount、componentDidUpdate和componentWillUnmount三个生命周期函数的执行时机。useEffect有两个参数:callback和dependencies。规则如下:当依赖不存在时,默认是所有state和props。即:回调将在每次渲染后执行。当依赖存在时,dependencies数组中的所有项,只要有任何一项发生变化,也会在触发componentDidUpdate后执行回调。当dependencies为空数组时,表示不依赖于任何state和props。即:useEffect只会在componentDidMount之后执行一次。state或props触发componentDidUpdate后不会执行回调。回调可以有一个返回值。返回值是一个函数。它会在执行componentWillUnmount时自动触发。依赖中定义的变量必须在回调函数中使用,否则声明依赖是没有意义的。依赖项通常是一个常量数组,而不是一个变量。因为一般在创建回调的时候,其实你很清楚里面会用到哪些依赖。React将使用浅比较来比较依赖项是否发生变化,因此要特别注意数组或对象类型。如果每次都新建一个对象,即使和之前的值相等,也会被认为是依赖变化。刚开始使用Hooks时,这是一个很容易导致错误的地方。这个方法会在页面更新后触发:importReact,{useState,useEffect}from'react';functionExample(){const[count,setCount]=useState(0);}useEffect(()=>{document.title=`您点击了${count}次`;});return(

你点击了{count}次

setCount(count+1)}>点击我
);}如果你某个数据变化想调用这个方法,后面需要指定一个数组,里面就是需要更新的值,变化的时候会触发这个方法。数组可以传多个值,一般Effect使用的所有props和state都会传入。//类组件方法componentDidUpdate(prevProps,prevState){if(prevState.count!==this.state.count){document.title=`你点击了${this.state.count}次`}}//函数组件钩子方法:aaauserEffect(()=>{document.title=`Youclicked${count}times`},[count])仅在计数发生变化时打印。如果我们想挂载的时候只触发一次,那么我们需要指定下面是一个空数组,那么就只会触发一次,适合我们进行ajax请求。userEffect(()=>{console.log('mounted')},[])如果我们想在组件销毁之前执行,那么我们需要在useEffect中返回一个函数。useEffect(()=>{console.log("mounted");return()=>{console.log('unmounted')}},[]);示例:在componentDidMount中订阅一个函数,在componentWillUnmount中取消订阅。//类组件编写classTestextendsReact.Component{constructor(props){super(props)this.state={isOnline:null}this.handleStatusChange=this.handleStatusChange.bind(this)}componentDidMount(){ChatApi.subscribeToFriendStatus(this.props.friend.id,this.handleStatusChange)}componentWillUnmount(){ChatApi.unsubscribeFromFriendStatus(this.props.friend.id,this.handleStatusChange)}handleStatusChange(status){this.setState({isOnline:status.isOnline})}render(){if(this.state.isOnline===null){return'loading...'}returnthis.state.isOnline?'Online':'Offline'}}//如何编写函数组件hooksfunctionTest1(props){const[isOnline,setIsOnline]=useState(null)useEffect(()=>{functionhandleStatusChange(status){setIsOnline(status.isOnline)}ChatApi.subscribeToFriendStatus(props.friend.id,handleStatusChange)//返回一个函数做额外的清理returnfunctioncleanup(){ChatApi.unsubscribeFromFriendStatus(props.friend.id,handleStatusChange)}})if(isOnline===null){return'loading...'}returnisOnline?'Online':'Offline'}useLayoutEffectuseLayoutEffectuseLayoutEffect的用法和useEffect完全一样,唯一的区别是useLayoutEffect的执行时机会阻塞页面的渲染。会保证在页面渲染之前执行,也就是说页面渲染出来的就是最终的结果。如果使用useEffect,页面可能会抖动,因为它被渲染了两次。在大多数情况下,只需使用useEffect。useContextuseContext可以轻松订阅上下文更改并在适当的时候重新渲染组件。获取上下文对象(React.createContext的返回值)并返回该上下文的当前值。context基础示例://因为祖先组件和后代组件都使用了这个ThemeContext,//可以单独放在一个js文件中,方便引入constThemeContext=React.createContext('light')classAppextendsReact.Component{render(){return()}}//中间层组件函数Toolbar(props){return(
)}classThemedButtonextendsReact.Component{//通过定义静态属性contextType进行订阅staticcontextType=ThemeContextrender(){return}}对于函数组件订阅方法:functionThemedButton(){//通过定义Consumer进行订阅value=useContext(ThemeContext)return}当你需要订阅多上下文,比较://传统实现方法functionHeaderBar(){return({user=>{notification=>
欢迎回来,{user.name}!您有{notifications.length}条通知。
}
}
)}//使用useContextfunctionHeaderBar(){constuser=useContext(CurrentUser)constnotifications=useContext(Notifications)return(
欢迎回来,{use.name}!你有{notifications.length}个通知。
)}useReduceruseReducer的用法与Redux非常相似。当state的计算逻辑比较复杂或者需要根据之前的值进行计算时,使用这个Hook比useState好functioninit(initialCount){return{count:initialCount}}functionreducer(state,action){switch(action.type){case'increment':return{count:state.count+1}case'decrement':返回{count:state.count-1}case'reset':returninit(action.payload)default:thrownewError()}}functionCounter({initialCount}){const[state,dispatch]=useReducer(reducer,initialCount,init)return(<>Count:{state.count}dispatch({type:'reset',payload:initialCount})}>重置dispatch({type:'increment'})}>+dispatch({type:'decrement'})}>-)}结合contextAPI,我们可以模拟Redux的操作:constTodosDispatch=React.createContext(null)constTodosState=React.createContext(null)functionTodosApp(){const[todos,dispatch]=useReducer(todosReducer)返回()}functionDeepChild(props){constdispatch=useContext(TodosDispatch)consttodos=useContext(TodosState)functionhandleClick(){dispatch({type:'add',text:'hello'})}return(<>{todos}AddTodo)}useCallback/useMemo/React.memouseCallback和useMemo是为性能优化而设计的。UseCallback缓存方法引用。useMemo缓存的是方法的返回值。使用场景是为了减少子组件不必要的渲染。//useCallbackfunctionFoo(){const[count,setCount]=useState(0)constmemoizedHandleClick=useCallback(()=>console.log(`Clickhappenedwithdependency:${count}`),[count],)returnClickMe}//useMemofunctionParent({a,b}){//只有当a改变时才重新渲染constchild1=useMemo(()=>,[a])//只有当b改变时才会重新渲染constchild2=useMemo(()=>,[b])return(<>{child1}{child2})}要实现类组件的shouldComponentUpdate方法,可以使用React.memo方法。不同的是它只能比较props,不能比较state。constParent=React.memo(({a,b})=>{//只会在a改变时重新渲染constchild1=useMemo(()=>,[a])//只有当b改变时才重新渲染constchild2=useMemo(()=>,[b])return(<>{child1}{child2})})return//class组件获取refclassTestextendsReact.Component{constructor(props){super(props)this.myRef=React.createRef()}componentDidMount(){this.myRef.current.focus()}render(){return}}//使用useReffunctionTest(){constmyRef=useRef(null)useEffect(()=>{myRef.current.focus()},[])return}useRef值的改变不会导致组件重绘,可以存储一些与界面显示无关的变量。函数式组件不能设置ref,如果你想保存一些值,可以使用React.forwardRef。importReact,{useState,useEffect,useRef,forwardRef}from'react'导出默认函数Home(props){consttestRef=useRef(null)useEffect(()=>{console.log(testRef.current)},[])return(
testRef.current?.setCount(8)}>添加父级我们都是中国人
)}/*eslint-disablereact/display-name*///constTest=forwardRef((props,ref)=>(////
测试模块//{props.children}//
//))constTest=forwardRef(functionTest(props,ref){const[count,setCount]=useState(1)useEffect(()=>{ref.current={count,setCount}})return(
当前计数:{count}
setCount(count+1)}>添加
)})customhookcustomHook是一个函数,但是名字必须以use开头,函数内部可以调用其他HookCustomHook是自然遵循Hook设计的约定,不是React的特性。和普通的hooks一样,只能用在函数组件或其他Hooks中。