hooks
React Hooks 是从 React 16.8 版本推出的新特性,目的是解决 React 的 状态共享以及组件生命周期管理混乱的问题。React Hooks 的出现标志着,React 不会再存在无状态组件的情况,React 将只有类组件和函数组件的概念。
userState 状态钩子
useState() 用于为函数组件引入状态
useState 函数入参只有一个,就是 state 的初始值,这个初始值可以是数字、字符串、对象,甚至是一个函数。useState 函数会返回一个数组,该数组包含 2 个元素:第 1 个元素为我们定义的变量,第 2 个元素为修改该变量对应的函数名称。
const [variable, setVariable] = useState(value);
setVariable(newValue); // 修改variable的值
代码示例:
function Example() {
const [count, setCount] = useState(0);
function increment() {
setCount(count + 1);
}
function decrement() {
// 入参为函数的情况,函数参数为上次变化的 state 结果
setCount((prevCount) => {
return prevCount - 1;
});
}
return (
<>
count: {count}
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</>
);
}
上述的代码示例能看到 setCount 的函数可以为函数,那什么时候能用到这种方式呢?
我们知道 setState 赋值过程是异步的,因此下面的代码最终只会执行一次 +1。这个时候就可以传入函数,拿到上次的 state 来进行操作。
for (let i = 0; i < 3; i++){
setCount(count + 1); // => setCount(prevCount => prevCount + 1);
}
如何处理数据类型为 Object 的情况?
在类组件中,setState 是执行的是 “异步对比累加赋值”。但 useState 定义的修改函数 setXxxx,执行的是 “异步直接赋值”。所以正确的做法是将完成的对象全部传入,如下示例
setPerson({...person, age: 18});
useEffect 副作用钩子
在类组件中有 componentDidMount(组件被挂载完成后)、componentDidUpdate(组件重新渲染完成后)、componentWillUnmount(组件即将被卸载前) 生命周期,hooks 提供了 useEffect 钩子补齐了函数组件的 “生命周期”。
useEffect(effect,[deps]) 函数可以传入 2 个参数,第 1 个参数为我们定义的执行函数、第 2 个参数是依赖关系(可选参数)。若一个函数组件中定义了多个 useEffect,那么他们实际执行顺序是按照在代码中定义的先后顺序来执行的。 代码形式:
useEffect(() => {
//此处编写 组件挂载之后和组件重新渲染之后执行的代码
...
return () => {
//此处编写 组件即将被卸载前执行的代码
...
}
},[deps])
‘[deps]’补充说明:
- 若缺省,则组件挂载、组件重新渲染、组件即将被卸载前,每一次都会触发该 useEffect;
- 若传值,则必须为数组,数组的内容是函数组件中通过 useState 自定义的变量或者是父组件传值过来的 props 中的变量,告诉 React 只有数组内的变量发生变化时才会触发 useEffect;
- 若传值,但是传的是空数组 [],则表示该 useEffect 里的内容仅会在 “挂载完成后和组件即将被卸载前” 执行一次;
代码示例:
// 1. 每当状态改变时,都要重新执行 useEffect 的逻辑:
useEffect(() => {
switchCount += 1
});
// 2. 每次状态都改变,也只执行第一次 useEffect 的逻辑:
useEffect(() => {
switchCount += 1
}, []);
// 3. 根据某个状态是否变化来决定要不要重新执行:
useEffect(() => {
switchCount += 1
}, [value]);
// 4. 组件卸载时处理一些内存问题,比如清除定时器、清除事件监听:
useEffect(() => {
const handler = () => {
document.title = Math.random().toString()
}
window.addEventListener('resize', handler)
return () => {
window.removeEventListener('resize', handler)
}
}, []);
useContext 共享状态钩子
useContext 是 React Hook 提供的跨级组件数据传递的一种方式,可以很方便的去订阅上下文的改变,并在合适的时候重新渲染组件。useContext 的使用方式如下:
function Component(){
const value = useContext(MyContext); // 在函数组件中声明一个变量来代表该共享数据对象的 value 值
return <div>{value.xxx}</div>
}
子组件(函数组件)需要先引入共享数据对象 MyContext,使用示例:
import React, { useContext } from 'react';
// 定义在全局下
const AppContext = React.createContext(); // 也可以设置默认值,如 React.createContext({name: 'Yang',age: 18});假如没有设置值,就会使用 Provider 上面定义的默认值
function AppComponent() {
<AppContext.Provider value=>
<Component />
</AppContext.Provider>
</div>
}
function Component(){
return <ChildComponent />
}
function ChildComponent(){
// 这里还可以使用 <AppContext.Consumer> 包裹组件来获取值,不过这种写法太麻烦了
const { name, age } = useContext(AppContext);
return <div>{name} - {age}</div>
}
useReducer Action 钩子
React 状态管理工具 Redux 由 Action、Reducer 和 Store 三个对象构成,而 Reducer 是唯一可以更新组件中 State 的途径。
useReducer 是 React Hooks 提供的一个函数,主要用来在某些复杂的场景中替换 useState。例如,包含复杂逻辑的 state 且包含多个子值,或者后面的 state 依赖于前面的 state 等。
useReducer 的语法格式如下:
const [state, dispatch] = useReducer(reducer, initialArg, init);
// 有时候需要惰性地创建初始 state,将将创建函数作为 init 传入第三个函数
function init(initialCount) {
return {count: initialCount};
}
使用示例:
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
React 源码中,useState 就是由 useReducer 实现的。我们还可以用 useReducer + useContext 实现 Redux 一样的效果。
什么时候用 useState / useReducer?
组件自己内部的简单逻辑变量用 useState、多个组件之间共享的复杂逻辑变量用 useReducer。
状态管理
React 的状态管理主要分三类:Local state、Context、第三方库。
Local State
React v16.8 之后,函数组件开发模式成为主流,hooks 丰富了函数组件的众多能力。 Local State 通过 useState 和 useReducer 来管理
useState - 更细粒度的状态
组件状态通过 useState 来进行管理 – useState hooks 将组件的状态进行更细粒度的拆分.
import {useState} from 'react';
const Foo = () => {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(count + 1)}>{count}</button>
</>
);
}
useReducer - 复杂逻辑抽象和复用
当 state 计算逻辑复杂或者存在共性时,我们可以优先考虑 useReducer.
例如常见的列表分页逻辑封装:
import { useReducer } from 'react';
const initialState = { pageNum: 1, pageSize: 15 };
const reducer = (state, action) => {
switch (action.type) {
case 'next': // 下一页
return { ...state, pageNum: state.pageNum + 1 };
case 'prev': // 前一页
return { ...state, pageNum: state.pageNum - 1 };
case 'changePage': // 跳转到某页
return { ...state, pageNum: action.payload };
case 'changeSize': // 更改每页展示条目数
return { pageNum: 1, pageSize: action.payload };
default:
return state;
}
};
const Page = () => {
const [pager, dispatch] = useReducer(reducer, initialState);
return (
<Table
pageNum={pager.pageNum}
pageSize={pager.pageSize}
onGoNext={() => dispatch({ type: 'next' })}
onGoPrev={() => dispatch({ type: 'prev' })}
onPageNumChange={num =>
dispatch({ type: 'changePage', payload: num })
}
onPageSizeChange={size =>
dispatch({ type: 'changeSize', payload: size })
}
/>
);
};
Context
使用 useContext + useReducer 来避免透传 prop 的情况。
import { createContext, useReducer, useContext } from 'react';
const ParentDispatch = createContext(null);
const Parent = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<ParentDispatch.Provider value={dispatch}>
<DeepTree parentState={state} />
</ParentDispatch.Provider>
);
};
// 深层子组件
const DeepChild = () => {
const dispatch = useContext(ParentDispatch);
const handleClick = () => {
dispatch({ type: 'add', payload: 'hello' });
};
return <button onClick={handleClick}>Add</button>;
};
状态管理库
常见的 React 状态管理库如 Redux、Mobx、Recoil. 参考文档
最新的一些状态管理库 Zustand、Recoil、Jotai,对比:2023 再谈前端状态管理