Hook使用规则
-
确保在React函数顶层使用Hooks,不能在循环、条件、嵌套函数中调用:当单个组件中有多个Hook时,React通过
Hooks的调用顺序
来保证Hooks的状态正确 - 只在函数组件或自定义Hook中调用
常用Hooks
useState
- 在函数组件内部使用
useState
,可以使该组件变成有状态组件
。这样在不使用class组件情况下也可以使用state及其他特性 - 使用方法:
const [state, setState] = useState(initialState);
参数:状态初始值,可以是变量或函数
- 变量:初始渲染期间,state与initialState的值相同
- 函数:如果初始参数需要通过复杂的计算得到,可以传入一个函数,返回initialState。当初始状态需要通过高性能操作后才能获得时,使用函数获取初始值可以提高性能:该函数只在初始渲染时被调用,在后续组件渲染中不会再调用
返回值:包含两个元素的数组,可解构
-
state
:状态值 -
setState
:状态更新函数。调用后,重新渲染组件,状态更新到最新
-
惰性初始state:
initialState
只会在组件的初始渲染中起作用,后续渲染时会被忽略 - 函数式更新:新的state依赖于先前的state时,可以为setState方法传入一个函数,函数接收先前的state,返回更新后的值
- 跳过state更新:若为setState传入当前state,React将跳过子组件的渲染及effect的执行
- 在多个useState调用中,渲染之间的调用顺序必须相同
-
函数式更新
可以防止合并更新问题
function Bulb(props) {
const [bulbState, setBulbState] = useState(false);
const switchBulb = () => {
setTimeout(() => {
setBulbState(!bulbState);
// setBulbState(bulbState => !bulbState);
}, 3000);
};
return (
<>
<div className={bulbState ? "bulbOn" : "bulbOff"}>我是灯泡</div>
<button onClick={switchBulb}>开关</button>
</>
);
}
- 使用
普通更新
:由于更新函数在setTimeout中调用,在3s内多次点击时,多次state更新会合并成一次,只改变一次state的状态
setBulbState(!bulbState);
- 使用
函数式更新
:在3s内多次(n)点击时,每次的更新函数确保收到的是最新的状态,所以会在3s后切换n次状态
setBulbState(bulbState => !bulbState);
useEffect
-
useEffect用于执行函数组件中的副作用(Side Effects)。副作用一般包含请求数据、事件处理、订阅、改变DOM等操作。React要求函数组件必须是一个纯函数,其中不能包含副作用。因此,React Hooks提供
useEffect
,以便在函数组件中执行副作用操作 -
useEffect
默认在每次渲染结束后执行 - 使用方法:
useEffect(() => {
// Todo
}, [xxx]);
参数:包含命令式、可能有副作用的函数。第二个参数可选,为副作用依赖值的数组
- 当无第二个参数时:每次组件渲染完成后都会执行
- 当第二个参数为
[]
时:空数组表示effect中没有依赖props或state中的值,因此仅执行一次(仅在组件挂载和卸载时执行)。类似于class组件中的componentDidMount
和componentWillUnmount
- 当第二个参数为变量数组时:当数组中的变量发生变化时,执行effect。如果数组中包含多个变量,当该次渲染的数组与上一次渲染的数组中的元素都相等时,会跳过本次effect的执行,但即使只有一个元素发生变化,也会执行。类似于
componentDidUpdate
中对prevProps
或prevState
的判断
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
注意:
- 每次渲染都执行effect可能会导致性能问题,利用useEffect的第二个参数可以跳过 Effect 的执行,从而进行性能优化
- 数组中包含的是所有外部作用域中会随时间变化并且在effect中使用的变量
- 两种副作用:
- 不需要清除的effect:在渲染完成后执行的一些额外的代码,执行后即可忽略。如发送网络请求,手动变更 DOM,记录日志等
- 需要清除的effect:如订阅外部数据源、定时器操作等,effect执行后要进行清除操作,以防止内存泄露
-
清除effect:不需要单独的effect来执行清除操作,只需在effect中返回一个清除函数,React将会在执行清除操作时调用该函数
清除时机:React会在组件卸载时清理effect,由于effect默认每次渲染时都会执行,所以React会在调用一个新的 effect 之前对前一个 effect 进行清理
useEffect(() => {
// Todo
return () => {
// 清除effect操作
}
});
- useEffect会在浏览器绘制后、新的渲染前执行,因此不会阻止浏览器的页面渲染
useContext
-
Context
为组件树提供数据共享的方法,数据无需层层传递,即不需要该数据的中间层组件无需作为“快递员”传递数据,避免props层层传递过于繁琐、组件关系维护困难。具体见文档
// 创建一个Context上下文
// defaultValue:初始共享值
const MyContext = React.createContext(defaultValue);
// Provider:提供者,提供共享数据
// value即共享数据
// 当value值发生变化时,Provider中所有使用该共享值的组件都会重新渲染
<MyContext.Provider value={xxx}>
</MyContext.Provider>
// Consumer:消费者,使用共享数据
<MyContext.Consumer>
{value => /* 基于 context 值进行渲染*/}
</MyContext.Consumer>
-
useContext
即为函数组件提供的获取共享数据的Hook - 使用方法:
const value = useContext(MyContext);
参数:通过React.createContext
创建的上下文组件对象
返回值:该上下文的当前值,即距离当前组件最近
的 <MyContext.Provider>
的value
示例:
const animal = {
dog: {
name: 'Kiki',
age: 1
},
cat: {
name: 'Sara',
age: 2
}
}
// 创建上下文,传入初始值
const PetContext = React.createContext(animal);
function App() {
return (
<PetContext.Provider value={animal.dog}>
<MyPet />
</PetContext.Provider>
);
}
// 使用共享数据的子组件
function MyPet() {
const pet = useContext(PetContext);
return (
<div>Hello, {pet.name}!</div> // Hello, Kiki!
);
}
- 使用Context的
Provider
组件包裹函数组件后,该函数组件才能共享上下文状态
<PetContext.Provider value={animal.dog}>
<MyPet />
</PetContext.Provider>
- 当组件依赖的(上层距离最近的)
Provider
的value值发生变化时,useContext
会触发重新渲染,获取最新的value值 - 使用
useContext
相当于在函数组件
中订阅Context上下文的变化
// 在class组件中的订阅方式:
static contextType = MyContext
// 或
<MyContext.Consumer>
useReducer
-
useReducer
是useState
的替代方案,与Redux
中的reducer
功能类似,都是提供状态改变功能;相对于useState
来说,useReducer
更适用于state逻辑复杂且包含多个子值(对象数组嵌套结构)的情景 - 使用方法:
const [state, dispatch] = useReducer(reducer, initialArg, init);
参数
-
reducer
:一个函数,接收当前状态state
和触发行为action
,返回计算后的新的状态newState
(state, action) => newState
action
:触发状态改变的行为,是一个对象,包含:
type:表示行为的描述,如增、删、改、查用户等;
payload(可选):表示携带的数据,用于newState的计算
const action = {
type: 'addUser',
payload: {
name: 'Bobo',
sex: 0
}
}
注意:
- 不要直接修改state!因为它是
immutable
(React 使用 Object.is 来比较 state
)- 可以通过
解构赋值
创建newState并返回
举个栗子:
function myReducer(state, action) {
const {type, payload} = action
if (type === 'addUser') {
return [
...state,
payload
]
}
if (type === 'removeUser') {
// 移除该用户,并把新的用户数组返回
// ...
}
// type的其他判断...
return state
}
-
initialArg
:初始状态值 -
init
:一个方法,用于惰性初始化state,当传该方法时,初始值就变成了init(initialArg)
,可以用来重置初始状态
返回值:包含两个元素的数组,可解构
-
state
:当前状态值 -
dispatch
:方法,用于触发事件以更新state,参数为一个action对象,即将action中的payload
作为参数,执行type操作,更改状态
// ...
const action = {
type: 'addUser',
payload: {
name: 'Bobo',
sex: 0
}
}
// ...
<button onClick={() => dispatch(action)}>点击增加用户</button>
- 跳过dispatch:若返回的state与当前的state相同,则不会进行子组件的重新渲染和执行副作用
-
useReducer
可以结合useContext
执行复杂的状态更新操作。当组件嵌套比较深时,可以将dispatch
作为共享数据传递给子组件,这样在子组件中就可以使用dispatch
触发事件,更改状态。参考此处
useCallback
-
useCallback
用于缓存回调函数,减少不必要的渲染 - 在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行,返回新的函数
- 使用方法:
useCallback(fn, deps)
参数:
-
fn
:函数 -
deps
:依赖项
返回值:缓存的函数
useMemo
- 同
useCallback
类似,useMemo
用于针对返回值进行缓存优化 - 在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行,返回新的值
- 使用方法:
useMemo(() => fn, deps)
参数:
-
fn
:函数 -
deps
:依赖项
返回值:缓存的值
useRef
- 先来了解一下Refs和DOM,在HTML元素或class组件上可以通过
React.createRef()
创建ref
引用该元素 - 在函数组件中引入了
useRef
,用于保存任何可变的值,每次都会返回相同的ref引用 - 使用方法:
const refContainer = useRef(initialValue);
参数:
-
initialValue
:初始值
返回值:可变的 ref 对象,其.current
属性被初始化为传入的参数initialValue