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;
// grandChildComponent
import {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的区别
引用
理解和引用的传送门👇