Hook 简介
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性 Hook 是一个特殊的函数,它可以让你“钩入” React 的特性
React 中内置的 Hook API
引入
import { useState, useEffect, useReducer, useCallback, useLayoutEffect, useDebugValue, useRef, useContext} from 'react';
基础 API
- useState
- useEffect
- useContext
1.useState
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>
增加
</button>
</div>
) }
2. useEffect
- 基础
function Example() {
useEffect(() => {
const interval = setInterval(() => {
}, 1000)
})
- 清除 在 React class 中,你通常会在 componentDidMount 中设置订阅,并在 componentWillUnmount 中清除它
function Example() {
useEffect(() => {
const interval = setInterval(() => {
}, 1000)
return () => {
clearInterval(interval)
}
})
return (
<>
Example组件
</>
);
}
effect 的条件执行:
- 默认情况下,effect 会在每轮组件渲染完成后执行
- 可以给 useEffect 传递第二个参数,它是 effect 所依赖的值数组
function Example(props) {
useEffect(() => {},[props.count])
return <>Example组件</>
}
useEffect 会在浏览器绘制后延迟执行,但会保证在任何新的渲染前执行。React 将在组件更新前刷新上一轮渲染的 effect
3. useContext
const TestContext= React.createContext({username: ''})
const Navbar = () => {
const {username} = useContext(TestContext)
return (
<div className="navbar">
<p>{username}</p>
</div>
)
}
const Messages = () => {
const {username} = useContext(TestContext)
return (
<div className="messages">
<p>1 message for {username}</p>
</div>
)
}
function Example() {
return (
<TestContext.Provider
value={{
username: 'superawesome',
}}
>
<div className="test">
<Navbar />
<Messages />
</div>
</TestContext.Provider>
)
}
额外的 Hook
- useReducer
- useRef
- useCallback
- useMemo
- useLayoutEffect
1. useReducer
useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法
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 Example() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
)
}
惰性初始化
const initialState = {count: 0};
function init(initialCount){
return {count:initialCount+1}
}
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 Example() {
const [state, dispatch] = useReducer(reducer, initialCount);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
2. useRef
它像一个变量, 类似于 this , 它就像一个盒子, 你可以存放任何东西. createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用
function Example() {
const inputEl:React.MutableRefObject<any> = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
3. useCallback
let count = 0;
function App() {
const [val, setVal] = useState('');
function getData() {
setTimeout(() => {
setVal('new data ' + count);
count++;
}, 500);
}
return <Child val={val} getData={getData} />;
}
function Child({val, getData}:any) {
useEffect(() => {
getData();
}, [getData]);
return <div>{val}</div>;
}
function App() {
const [val, setVal] = useState('');
const getData = useCallback(() => {
setTimeout(() => {
setVal('new data ' + count);
count++;
}, 500);
}, []);
return <Child val={val} getData={getData} />;
}
function Child({val, getData}:any) {
useEffect(() => {
console.log('%c 🍩 useEffect: ', 'font-size:20px;background-color: #2EAFB0;color:#fff;', 'useEffect');
getData();
}, [getData]);
return <div>{val}</div>;
}
4. useMemo
- useMemo和useCallback的区别 及使用场景
- useMemo 和 useCallback 接收的参数都是一样,第一个参数为回调 第二个参数为要依赖的数据
共同作用:
- 仅仅 依赖数据 发生变化, 才会重新计算结果,也就是起到缓存的作用。
两者区别:
- useMemo 计算结果是 return 回来的值, 主要用于 缓存计算结果的值 ,应用场景如: 需要 计算的状态
- useCallback 计算结果是 函数, 主要用于 缓存函数,应用场景如: 需要缓存的函数,因为函数式组件每次任何一个 state 的变化 整个组件 都会被重新刷新,一些函数是没有必要被重新刷新的,此时就应该 缓存起来,提高性能,和减少资源浪费
5. useLayoutEffect
其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
- 使用 useEffect
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`useEffect - count=${count}`)
const pre = Date.now();
while (Date.now() - pre < 500) {}
if (count === 0) {
setCount(10 + Math.random() * 200);
}
}, [count]);
return (
<div onClick={() => setCount(0)}>{count}</div>
);
}
- 使用 useLayoutEffect
function Example() {
const [count, setCount] = useState(0);
useLayoutEffect(() => {
console.log(`useLayoutEffect - count=${count}`)
const pre = Date.now();
while (Date.now() - pre < 500) {}
if (count === 0) {
setCount(10 + Math.random() * 200);
}
}, [count]);
return (
<div onClick={() => setCount(0)}>{count}</div>
);
}
- useLayoutEffect和componentDidMount、componentDidUpdate触发时机一致
- useEffect在浏览器渲染完成后执行
- useLayoutEffect在浏览器渲染前执行
- useLayoutEffect总是比useEffect先执行
- useLayoutEffect里面的任务最好影响了Layout(布局)
自定义 Hook
const useWinSize = () => {
// 1. 使用useState初始化窗口大小state
const [size, setSize] = useState({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
});
const changeSize = useCallback(() => {
// useCallback 将函数缓存起来 节流
setSize({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
});
}, []);
// 2. 使用useEffect在组件创建时监听resize事件,resize时重新设置state (使用useCallback节流)
useEffect(() => {
// 绑定一次页面监听事件 组件销毁时解绑
window.addEventListener('resize', changeSize);
return () => {
window.removeEventListener('resize', changeSize);
};
}, []);
return size;
};
Hook 规则
只在最顶层使用 Hook
不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确
//错误
function Example() {
if (1<2) {
const [count, setCount] = useState(0);
}
return (
<div>Example </div>
)
}
只在 React 函数中调用 Hook
- 不要在普通的 JavaScript 函数中调用 Hook。你可以
- 在 React 的函数组件中调用 Hook
- 在自定义 Hook 中调用其他 Hook