React Hooks
Hooks
是React16.8
新增的特性,可以在不编写class
的情况下使用state
以及其他特性,Hooks
的出现主要是解决组件之间的复用状态逻辑困难,复杂组件难理解等问题
Hook 规则
- 只在最顶层使用
Hook
,即:不要在循环,条件判断或嵌套函数中调用Hook
- 只在
React
函数中调用Hook
State Hook:useState
使用
useState
创建一个响应式的状态,接收一个初始值(可以是函数且函数只在初始渲染时调用)
,返回一个数组,数组第一个元素是state
,第二个元素是更新state
的函数。如果需要依据当前的state
或props
更新state
,则更新state
的函数可以接收一个函数,函数的参数是当前的state
,返回一个需要更新的值。需要多个状态时则使用useState
创建多个state
即可
import React, { useState } from 'react';
const App = () => {
const [count, setCount] = useState(0);
const onDec = () => setCount((count) => count - 1);
const onAdd = () => setCount((count) => count + 1);
const onReset = () => setCount(0);
return (
<>
<button onClick={onDec}>-</button>
<span>{count}</span>
<button onClick={onAdd}>+</button>
<button onClick={onReset}>reset</button>
</>
);
};
export default App;
Effect Hook:useEffect
useEffect
代替了生命周期componentDidMount
、componentDidUpdate
、componentWillUnmount
。其接收两个参数:第一个参数是一个函数,此函数就相当于componentDidMount
和componentDidUpdate
,此函数可返回一个函数,返回的函数相当于componentWillUnmount
。第二个参数是一个数组(非必传),是Effect Hook
的依赖项,如果不传,则组件首次挂载后以及state
发生变化后会触发,如果传空数组,则只在组件首次挂载后触发,如果数组中传入依赖项,则组件首次挂载后和依赖项发生变化时会触发。可使用多个useEffect
,执行顺序是声明的顺序
import React, { useEffect, useState } from 'react';
const App = () => {
const [count, setCount] = useState(0);
const onDec = () => setCount((count) => count - 1);
const onAdd = () => setCount((count) => count + 1);
const onReset = () => setCount(0);
const [message, setMessage] = useState('React');
const onChangeMessage = () => setMessage('Vue');
useEffect(() => console.log('只会在组件挂载后触发'), []);
useEffect(() => console.log('组件挂载后及状态发生变化后触发'));
useEffect(() => console.log('组件挂载后及count变化后触发'), [count]);
useEffect(() => {
const timer = setInterval(() => {
console.log('定时器运行中...');
}, 1000);
return () => {
console.log('组件卸载前触发');
clearInterval(timer);
};
});
return (
<>
<button onClick={onDec}>-</button>
<span>{count}</span>
<button onClick={onAdd}>+</button>
<button onClick={onReset}>reset</button>
<div onClick={onChangeMessage}>{message}</div>
</>
);
};
export default App;
useContext
接收一个
Context
对象(React.createContext
的返回值)并返回当前context
的值,当前的context
的值取决于上层组件中距离最近的Context.Provider
的value
属性决定
import React, { createContext, useContext, useState } from 'react';
interface IProvider {
count: number;
onDec: () => void;
onAdd: () => void;
}
const context = createContext<IProvider | null>(null);
const { Provider } = context;
const Counter = () => {
const { count } = useContext(context) as IProvider;
return <span>{count}</span>;
};
const CountButton = (props: any) => {
const { onDec, onAdd } = useContext(context) as IProvider;
return <button onClick={props.type === 'dec' ? onDec : onAdd}>{props.children}</button>;
};
function App() {
const [count, setCount] = useState(0);
const provider = {
count,
onDec: () => setCount((count) => count - 1),
onAdd: () => setCount((count) => count + 1)
};
return (
<Provider value={provider}>
<CountButton type="dec">-</CountButton>
<Counter />
<CountButton type="add">+</CountButton>
</Provider>
);
}
export default App;
useReducer
useState
的替代方案,第一个参数接收像(state,action) => newState
的reducer
,第二个参数接收一个初始值,返回当前的state
和dispatch
方法
import React, { useReducer } from 'react';
const reducer = (state: any, action: { type: string }) => {
switch (action.type) {
case 'dec':
return { count: state.count - 1 };
case 'add':
return { count: state.count + 1 };
}
};
const App = () => {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<>
<button onClick={() => dispatch({ type: 'dec' })}>-</button>
<span>{state?.count}</span>
<button onClick={() => dispatch({ type: 'add' })}>+</button>
</>
);
};
export default App;
useCallback
把内联回调函数及依赖项数组作为参数传入
useCallback
,返回该回调函数的memoized
版本的回调函数。用于性能优化(可缓存函数
),一般是在父组件中通过useCallback
缓存函数,当父组件内的数据发生变化重新渲染时,只要不是useCallback
的依赖项发生变化,子组件就不会重新渲染,子组件需要通过React.memo
处理一下。
import React, { memo, useCallback, useEffect, useState } from 'react';
const Message = memo((props: { msg: string; onChange: () => void }) => {
console.log('Message渲染');
return <span onClick={props.onChange}>Message组件:{props.msg}</span>;
});
const App = () => {
const [count, setCount] = useState(0);
const onDec = () => setCount((count) => count - 1);
const onAdd = () => setCount((count) => count + 1);
const [msg, setMsg] = useState('hello');
// const onChangeMsg = () => setMsg('world')
const onChangeMsg = useCallback(() => setMsg('world'), [msg]);
return (
<>
<button onClick={onDec}>-</button>
<span>{count}</span>
<button onClick={onAdd}>+</button>
<Message msg={msg} onChange={onChangeMsg} />
</>
);
};
export default App;
如果不使用
useCallback
,则当改变count
是也会触发Message
组件的渲染,当使用useCallback
之后,只有当msg
发生变化时,Message
组件才会重新渲染
useMemo
把函数和依赖项数组作为参数传入
useMemo
,返回该回调函数的memoized
版本的回调函数,它仅在某个依赖项改变时才会重新计算memoized
值。用于性能优化(可缓存数据
),如果一个组件内存在多个可变数据,当一个数据发生变化则所有数据的处理函数都会被调用,使用useMemo
可将某些操作绑定到指定数据,即:指定依赖,当依赖发生变化时才会重新执行操作。
import React, { useMemo, useState } from 'react';
const App = () => {
const [count, setCount] = useState(0);
const onDec = () => setCount((count) => count - 1);
const onAdd = () => setCount((count) => count + 1);
const [msg, setMsg] = useState('Hello');
const onChangeMsg = () => setMsg('Hi');
// const getFullMsg = () => {
// console.log('getFullMsg调用')
// return `${msg} World`
// }
const getFullMsg = useMemo(() => {
console.log('getFullMsg调用');
return `${msg} World`;
}, [msg]);
return (
<>
<div>
<button onClick={onDec}>-</button>
<span>{count}</span>
<button onClick={onAdd}>+</button>
</div>
<div>
<div>{msg}</div>
<button onClick={onChangeMsg}>改变msg:{getFullMsg}</button>
</div>
</>
);
};
export default App;
上述例子如果不使用useMemo
,则更新count
也会触发getFullMsg
函数
特别需要注意的是:
useMemo
返回的是一个值,不是一个函数
useRef
返回一个可变的
ref
对象,其.current
属性被初始化为传入的数据.一般用于获取DOM
元素
import React, { useEffect, useRef } from 'react';
const App = () => {
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current!.focus();
});
return <input type="text" ref={inputRef} />;
};
export default App;
useImperativeHandle
可以在使用
ref
时自定义暴露给父组件的实例值,需要和forwardRef
配合使用.在子组件暴露指定的内容,父组件通过ref.current
获取当前暴露的内容
import React, { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
const Input = forwardRef((_, ref) => {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
autoFocus: () => inputRef.current?.focus()
}));
return <input type="text" ref={inputRef} />;
});
const App = () => {
const inputRef = useRef<any>(null);
useEffect(() => inputRef.current?.autoFocus());
return <Input ref={inputRef} />;
};
export default App;
useLayoutEffect
同
useEffect
一样,不过useLayoutEffect
是等所有的DOM
更新之后同步调用effect
.
useDebugValue
用于 React 开发者工具中显示自定义
hook
的标签