8大hooks概念、使用场景
前言
对不同阶段的react开发者会有不同的效果,最终目的是能够对8大react hooks,完全理解,游刃有余。对比useState和useReducer,什么时候使用useMemo和useCallback,useEffect的参数… …
useState
概念
定义类组件的状态
相关问题
为什么usestate是数组
如果 useState 返回的是数组,那么使用者可以对数组中的元素命名,代码看起来也比较干净 如果 useState 返回的是对象,在解构对象的时候必须要和 useState 内部实现返回的对象同名,想要使用多次的话,必须得设置别名才能使用返回值
useEffect
概念
- 副作用hooks
- 给没有生命周期的组件,添加结束渲染的信号
- 执行时机:在渲染结束之后执行
- 不管什么场景下,useEffect都会在初次渲染期间执行
示例
import React,{ useEffect, useState } from 'react'function StateFunction () {const [num, setNum] = useState(0)useEffect( () => {console.log('函数式组件结束渲染')const updateMouse = (e) => {console.log('打印当前位置')setPositions({ x:e.clientX, y:e.clientY })}document.addEventListener('click',updateMouse) // 添加绑定方法事件(要修改依赖,绑定到依赖上)return () => {// 在每次执行useEffect之前都会执行上一次return中内容document.removeEventListener('click',updateMouse)// 移除绑定方法事件(要修改依赖,绑定到依赖上)console.log('1111销毁')}})
}
1.第一个参数,接收一个函数作为参数
2.第二个参数,接收【依赖列表】,只有依赖更新时,才会执行函数
3.返回一个函数,先执行返回函数,再执行参数函数
useLayoutEffect
概念
一般将useLayoutEffect称为有DOM操作的副作用hooks。作用是在DOM更新完成之后执行某个操作。执行时机:在DOM更新之后执行。
与eusEffect不同点:执行示例代码会发现useLayoutEffect永远比useEffect先执行,这是因为DOM更新之后,渲染才结束或者渲染还会结束。
useMemo
概念
传递一个创建函数和依赖项,创建函数会需要返回一个值,只有在依赖项发生改变的时候,才会重新调用此函数,返回一个新的值
useCallback
概念
父组件的传入函数不更新,就不会触发子组件的函数重新执行
示例
function Parent () {const [num, setNum] = useState(1)const [age, setAge] = useState(18)const getDoubleNum = useCallback( () => {console.log(`获取双倍Num${num}`)return 2 * num},[num] )return (<div onClick={ () => {setNum( num => num+1 )} }>这是一个函数式组件————num:{ getDoubleNum() }<br></br>age的值为————age:{ age }<br></br>set.size:{set.size}<Child callback={ getDoubleNum() }></Child></div>)
}function Child(props) {useEffect( () => {console.log('callback更新了') //这里代表的是需要跟随传入内容的改变而同步进行的操作},[props.callback])return (<div>子组件的getDoubleNum{props.callback}</div>)
}
总结
在子组件不需要父组件的值和函数的情况下,只需要使用memo
函数包裹子组件即可
如果有函数传递给子组件,使用useCallback
缓存一个组件内的复杂计算逻辑需要返回值时,使用useMemo
useRef
概念
返回一个子元素索引,此索引在整个生命周期中保持不变。
作用也就是:长久保存数据。
注意事项,保存的对象发生改变,不通知。属性变更不会重新渲染
示例
const [num, setNum] = useState(0)const ref = useRef()
useEffect( () => {ref.current = setInterval( () => {setNum( num => num+1 )},400 )// ref.current = '111'
},[] )useEffect( () => {if(num > 10){console.log('大于10了,清除定时器')console.log('ref.current',ref.current)clearTimeout(ref.current)}
},[num] )return (<div>这是一个函数式组件————num:{ num }</div>
)。
useContext
概念
useContext是让子组件之间共享父组件传入的状态的,特别是推荐应用程序需要在比两层更深的组件之间共享状态时使用,或者父组件有传入一个值到多个不同的子组件中。
示例
一个值到多个的使用前
function StateFunction () {const [num, setNum] = useState(1)return (<div><button onClick={ ()=> setNum(num => num+1) }>增加num的值+1</button><br></br>这是一个函数式组件——num:{ num }<Item1 num={num}></Item1><Item2 num={num}></Item2>// ......</div>)
}function Item1 (props) {return (<div>子组件1 num:{ props.num }</div>)
}function Item2 (props) {return (<div>子组件2 num:{ props.num }</div>)
}
一个值到多个使用后
const Context = createContext(null)function StateFunction () {const [num, setNum] = useState(1)return (<div><button onClick={ ()=> setNum(num => num+1) }>增加num的值+1</button><br></br>这是一个函数式组件——num:{ num }<Context.Provider value={num}><Item3></Item3><Item4></Item4></Context.Provider></div>)
}function Item3 () {const num = useContext(Context)return (<div>子组件3: { num }</div>)
}function Item4 () {const num = useContext(Context)return (<div>子组件4: { num+2 }</div>)
}
一传多优化总结
使用useContext优化后,代码如下,这样我们只需要在子组件中使用useContext(Context句柄)来获取数据即可,添加同类子组件时不需要再关注父组件中子组件定义时的props传入值,使用方法如下
需要引入useContetx,createContext两个内容
通过createContext创建一个context句柄
Context.Provider来确定数据共享范围
通过value来分发内容
在子组件中,通过useContext(Context句柄)来获取数据
注意事项,上层数据发生改变,肯定会触发重新渲染(点击button按钮触发父组件更新传入的num值能看到子组件重新渲染)
两层以上子组件传递
const ContextA = createContext(null);const Parent = () => {const [stateA, dispatchA] = useReducer(reducerA, initialStateA);const valueA = useMemo(() => [stateA, dispatchA], [stateA]);return (<ContextA.Provider value={valueA}><Child1 /></ContextA.Provider>);
};const Child1 = () => (<GrandChild1 />
);const GrandChild1 = () => (<GrandGrandChild1 />
);const GrandGrandChild1 = () => {const [stateA, dispatchA] = useContext(ContextA);return (...);
};
多个值的Context
🙅👇
🙆下面
推荐使用这个开源库【 react-hooks-global-state】
import { createGlobalState } from 'react-hooks-global-state';const initialState = { a: ...,b: ...,c: ...,
};
const { GlobalStateProvider, useGlobalState } = createGlobalState(initialState);const App = () => (<GlobalStateProvider>...</GlobalStateProvider>
);const Component1 = () => {const [valueA, updateA] = useGlobalState('a');return (...);
};
Q: 如何跨文件使用Context这个变量
A: 把这个Ctx 直接暴露出去 即 export const Ctx = createContext();
伪代码:
// ParentComponent
export let Context = createContext(null)
const ParentComponent = ()=>{
let [num, setNum] = useState(1)
return (<div><Context.Provider value={{num,setNum}}<grandChild show={True}></Context.Provider ><button onClick=()=>setNum(num + 1)>+1</button></div>)
}
export default ParentComponent;// grandChildComponentimport {Context} form './ParentComponent'
const grandChildComponent = ()=>{return (<Context.Consumer>{value=>(<div><span>{value.num}</span><button onClick={()=>value.setNum(value.num+1)}>+1</button></div>)}</Context.Consumer>)
}
export default grandChildComponent;
具体写法可以参考隔壁
useReducer
概念
useReducer通过向下传递分派(而不是回调)让子组件之间共享父组件传入的状态。涉及多个子值的复杂状态逻辑或下一个状态依赖于前一个状态时,可以优化触发深度更新的组件的性能。
使用useReducerwhich 返回一个dispatch在重新渲染之间不会改变的方法,并且您可以在 reducer 中拥有操作逻辑。
示例
const store = {age:18,num:1
} // 数据仓库const reducer = (state, action) => {switch(action.type){case 'add':return {...state,num: action.num+1}default:return {...state}}
} // 管理者function StateFunction () {const [state,dispacth] = useReducer(reducer,store) // ①return (<div><button onClick={ () => {dispacth({type: 'add',num: state.num})} }>增加num的值+1</button><br></br>这是一个函数式组件——num:{ state.num }</div>)
}
即是:
创建数据仓库store(状态)和管理者reducer(动作),定义一个数组获取状态和改变状态的动作,触发动作的时候需要传入type类型判断要触发reducer哪个动作,然后进行数据的修改。需要注意的地方是,在reducer中return的对象中,需要将state解构,否则状态就剩下一个num值了
与useState的区别
引用
理解和引用的传送门👇
- 掌握及对比常用的8个Hooks(优化及使用场景)
- Four patterns for global state with React hooks: Context or Redux
- React Hooks: useState 和 useReducer 有什么区别?
- Hooks API Reference
- useState vs useReducer
- usestate vs usereducer hooks