媒介:一直对这个新特征异常感兴趣,终究本日有时候,花了大半天时候,把
Hooks
的官方教程过了一遍,收成颇多,赞叹这个新特征真 TM 好用,今后开辟用这个怕是要起飞了😆。
状况钩子(State Hook)
const [state, setState] = useState(initialState);
- 多个
useState
时,React
依赖于每次衬着时钩子的挪用递次都是一样的(存在与每一个组件关联的“存储单元”的内部列表寄存JavaScript对象),从而完成钩子与状况的一一对应关联。 -
setState()
吸收新的state
或许一个返回state
的函数(setCount(prevCount => prevCount - 1)}
)。 - 不同于类组件中的
setState
,useState
返回的setState
不会自动兼并更新对象到旧的state
中(能够运用useReducer
)。 -
useState
能够吸收一个函数返回initialState
,它只会在首次衬着时被挪用。 - 当
setState
中的state
和当前的state
相称(经由过程Object.is
推断),将会退出更新。 - 发起将一个状况依据哪些须要值一同变化拆分为多个状况变量。
❌用法:
const [rows, setRows] = useState(createRows(props.count)); // `createRows()`每次将会衬着将会被挪用
✅用法:
const [rows, setRows] = useState(() => createRows(props.count)); // `createRows()`只会被挪用一次
个中的() => createRows(props.count)
会赋值给rows
,如许就保证了只要在rows
挪用时,才会建立新的值。
作用钩子(Effect Hook)
useEffect(didUpdate);
- 相当于性命周期函数
componentDidMount
,componentDidUpdate
,componentWillUnmount
的组合。 - 能够返回一个函数(
cleanup
)用于清算。 - 每次从新衬着都将会发作
cleanup phase
⏬
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
// ====== 缘由在这里 ======
componentDidUpdate(prevProps) {
// Unsubscribe from the previous friend.id
ChatAPI.unsubscribeFromFriendStatus(
prevProps.friend.id,
this.handleStatusChange
);
// Subscribe to the next friend.id
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
// Mount with { friend: { id: 100 } } props
ChatAPI.subscribeToFriendStatus(100, handleStatusChange); // Run first effect
// Update with { friend: { id: 200 } } props
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // Clean up previous effect
ChatAPI.subscribeToFriendStatus(200, handleStatusChange); // Run next effect
// Update with { friend: { id: 300 } } props
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // Clean up previous effect
ChatAPI.subscribeToFriendStatus(300, handleStatusChange); // Run next effect
// Unmount
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // Clean up last effect
-
useEffect(() => {document.title = You clicked ${count} times;}, [count]);
,指定第二个参数(这里为[count
])变化时才发作cleanup phase
,然后实行effect
; - 上面状况,假如
useEffect
第二个参数为为[]
则示意只运转一次(componentDidMount
中实行effect
,componentWillUnmount
中举行cleanup
),永久不从新运转。 - 和
componentDidMount
/componentDidUpdate
有区分的处所在于,useEffect
中的函数会在layout
和paint
完毕后才被触发。(能够运用useLayoutEffect
鄙人一次衬着之前(即 DOM 突变以后)同步触发) -
useEffect
虽然被推晚到浏览器绘制完成以后,然则一定在有任何新的显现之前启动。由于React
总是在最先更新之前革新之前衬着的结果。
其他钩子
useContext
const context = useContext(Context);
接收一个上下文对象(由React.createContext
建立),返回当前上下文值(由近来的上下文供应)。
附加钩子(Additional Hooks)
基础钩子的变体或用于特定边沿状况的钩子。
useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init);
- 第三个参数
init
为函数,将会如许挪用:init(initialArg)
,返回初始值。 - 假如返回
state
和如今的state
一样,将会在不影响子孙或许触发结果的状况下退出衬着。
useCallback
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
传入一个内联回折衷一个输入数组,返回一个带有影象的函数,只要输入数组中个中一个值变化才会变动。useCallback(fn, inputs)
等价于 useMemo(() => fn, inputs)
。
useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
传入一个建立函数和一个输入数组,返回一个带有影象的值,只要输入数组中个中一个值变化才会从新盘算。
useRef
const refContainer = useRef(initialValue);
// ...
<input ref={refContainer} />
...
返回一个可变的ref
对象,能够自动将ref
对象中的current
属性作为初始值通报的参数,坚持到组件的全部性命周期。
与在类中运用实例字段的体式格局相似,它能够保留任何可变值。
如保留前一个状况:
function Counter() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
});
const prevCount = prevCountRef.current;
return <h1>Now: {count}, before: {prevCount}</h1>;
}
useImperativeHandle
useImperativeHandle(ref, createHandle, [inputs])
自定在运用 ref 时,公开给父组件的实例值,必需和forwardRef
一同运用。
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
<FancyInput ref={fancyInputRef} />
// 挪用
fancyInputRef.current.focus()
useLayoutEffect
运用方法和useLayoutEffect
一致,不过它是在 DOM 读取规划时同步触发(相当于componentDidMount
和componentDidUpdate
阶段)。(发起尽量运用useEffect
防止壅塞可视化更新)
useDebugValue
useDebugValue(value)
用于在React DevTools
中显现自定义钩子的标签,关于自定义钩子中用于同享的部份有更大代价。
自定义显现花样:
useDebugValue(date, date => date.toDateString());
钩子(Hooks)划定规矩
1. 只能在顶层挪用,不能再循环、前提语句和嵌套函数中运用。 (缘由:State Hook 第1条)
准确做法:
useEffect(function persistForm() {
// 👍 We're not breaking the first rule anymore
if (name !== '') {
localStorage.setItem('formData', name);
}
});
2. 只能在React
函数组件中被挪用。(能够经由过程自定义钩子函数处理)
能够运用eslint-plugin-react-hooks来强迫自动实行这些划定规矩。
自定义钩子(Hook)
- 以
use
开首,一种条约。 - 自定钩子是一种复用状况逻辑的机制(比方设置定阅和记着当前值),每次运用,内部一切状况和作用都是自力的。
- 自定义钩子每一个状况自力的才能源于
useState
和useEffect
是完整自力的。
测试钩子(Hook)
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
import React from 'react';
import ReactDOM from 'react-dom';
import { act } from 'react-dom/test-utils';
import Counter from './Counter';
let container;
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
document.body.removeChild(container);
container = null;
});
it('can render and update a counter', () => {
// Test first render and effect
act(() => {
ReactDOM.render(<Counter />, container);
});
const button = container.querySelector('button');
const label = container.querySelector('p');
expect(label.textContent).toBe('You clicked 0 times');
expect(document.title).toBe('You clicked 0 times');
// Test second render and effect
act(() => {
button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
});
expect(label.textContent).toBe('You clicked 1 times');
expect(document.title).toBe('You clicked 1 times');
});