Hook可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
useState
useState 就是一个 Hook
通过在函数组件里调用它来给组件添加一些内部 state,React 会在重复渲染时保留这个 state
useState 会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state 进行合并
useState 唯一的参数就是初始 state
返回一个 state,以及更新 state 的函数
在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同
setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列
let hookState = []; // 这是全局变量,用来记录hook的值
let hookIndex = 0; // 存放当前hook的索引值
let scheduleUpdate;
function render(vdom, container) {
mount(vdom,container);
// react里不管哪里触发的更新,真正的调度都是从根节点开始的
scheduleUpdate = ()=>{
hookIndex = 0; // 索引值重置为0
// 从根节点执行完整的dom-diff,进行组件更新
compareTwoVdom(container,vdom,vdom);
}
}
function useState(initailState) {
hookState[hookIndex] = || hookStates[hookIndex]||initialState;
let currentIndex = hookIndex;
function setState(newState){
if(typeof newState === 'function') newState=newState(hookStates[currentIndex]);
hookStates[currentIndex]=newState;
scheduleUpdate(); // 状态变化后,要执行调度更新任务
}
return [hookStates[hookIndex],setState];
}
useCallbackuseMemo
把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新
把创建函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算
let Child = ({data,handleClick})=>{
console.log('Child render');
return (
<button onClick={handleClick}>{data.number}</button>
)
}
Child = React.memo(Child);
function App(){
console.log('App render');
const[name,setName]=React.useState('zhufeng');
const[number,setNumber]=React.useState(0);
let data = React.useMemo(()=>({number}),[number]);
let handleClick = React.useCallback(()=> setNumber(number1),[number]);
return (
<div>
<input type="text" value={name} onChange={event=>setName(event.target.value)}/>
<Child data={data} handleClick={handleClick}/>
</div>
)
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
/**
* factory 可以用来创建对象的工厂方法
* deps 依赖数组
*/
export function useMemo(factory,deps){
if(hookStates[hookIndex]){
let [lastMemo,lastDeps] = hookStates[hookIndex];
let same = deps.every((item,index)=>item === lastDeps[index]);
if(same){
hookIndex;
return lastMemo;
}else{
let newMemo = factory();
hookStates[hookIndex]=[newMemo,deps];
return newMemo;
}
}else{
let newMemo = factory();
hookStates[hookIndex]=[newMemo,deps];
return newMemo;
}
}
export function useCallback(callback,deps){
if(hookStates[hookIndex]){
let [lastCallback,lastDeps] = hookStates[hookIndex];
let same = deps.every((item,index)=>item === lastDeps[index]);
if(same){
hookIndex;
return lastCallback;
}else{
hookStates[hookIndex]=[callback,deps];
return callback;
}
}else{
hookStates[hookIndex]=[callback,deps];
return callback;
}
}
useReducer
useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法
在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等
function reducer(state= {number:0}, action){
switch(action.type){
case 'ADD':
return {number:state.numberr 1}
case 'MINUS':
return {number:state.numberr - 1}
default:
return state;
}
}
function Counter() {
const [state, dispatch] = React.useReducer(reducer,{number:0})
return (
<div>
Count: {state.number}
<button onClick={() => dispatch({type: 'ADD'})}></button>
<button onClick={() => dispatch({type: 'MINUS'})}>-</button>
</div>
)
}
useState就是useReducer的一个语法糖
useContext
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值
当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定
当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值
useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者 <MyContext.Consumer>
useContext(MyContext) 只是让你能够读取 context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context
function Counter(){
let {state,dispatch} = React.useContext(CounterContext);
return (
<div>
<p>{state.number}</p>
<button onClick={() => dispatch({type: 'add'})}></button>
<button onClick={() => dispatch({type: 'minus'})}>-</button>
</div>
)
}
function App(){
const [state, dispatch] = React.useReducer(reducer, {number:0});
return (
<CounterContext.Provider value={{state,dispatch}}>
<Counter/>
</CounterContext.Provider>
)
}
useEffect
- 在函数组件主体内(这里指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性
- 使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。
- useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API
- 该 Hook 接收一个包含命令式、且可能有副作用代码的函数
function Counter() {
console.log('Counter render')
const [number, setNumber] = React.useState(0);
React.useEffect(() => {
console.log('开启一个新的定时器')
const $timer = setInterval(() => {
console.log('执行定时器')
setNumber(number => number 1); // 用依赖前一个状态的函数,避免闭包setNumber(number1)number一直是定义时的值0
}, 1000);
return () => {
console.log('销毁老的定时器');
clearInterval($timer);
}
});
return (
<p>{number}</p>
)
}
ReactDOM.render(<Counter />, document.getElementById('root'));
/**
这是最难用好的一个effect
每次渲染都会开启一个新的定时器
1、加上依赖【】数组。定时器只会执行一次
2、可在下一次执行effect之前清除上一个定时器
Counter render
开启一个新的定时器
执行定时器
Counter render
销毁老的定时器
开启一个新的定时器
执行定时器
*/
export function useEffect(callback,dependencies){
// 先判断是不是初次渲染
if(hookStates[hookIndex]){
let [lastCallback,lastDeps] = hookStates[hookIndex];
let same = dependencies&&dependencies.every((item,index)=>item === lastDeps[index]);
if(same){
hookIndex++;
}else{
// 如果有任何一个值不一样,则执行上一个的销毁函数
lastCallback&&lastCallback();
// 开启一个新的宏任务
setTimeout(()=>{
lastCallback = callback();
hookStates[hookIndex++]=[lastCallback,dependencies];
});
}
}else{
// 如果是第一次执行 执行到此
setTimeout(()=>{
let destroy = callback();
hookStates[hookIndex++]=[callback(),dependencies];
});
}
}
useLayoutEffect+useRef
- 其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect
- useEffect不会阻塞浏览器渲染,而 useLayoutEffect 会阻塞浏览器渲染
- useEffect会在浏览器渲染结束后执行,useLayoutEffect 则是在 DOM 更新完成后,浏览器绘制之前执行
forwardRef+useImperativeHandle
- forwardRef将ref从父组件中转发到子组件中的dom元素上,子组件接受props和ref作为参数
- useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值
function Child(props, ref) {
const inputRef = React.useRef();
// 这个方法可以定制暴露给父组件ref值,forwardRef.current = {focus}
React.useImperativeHandle(ref, () => (
{
focus() {
inputRef.current.focus();
}
}
));
return (
<input type="text" ref={inputRef} />
)
}
function Parent(){
let [number, setNumber] = React.useState(0);
const inputRef = React.useRef();
function getFocus() {
console.log(inputRef.current);
inputRef.current.value = 'focus';
inputRef.current.focus();
}
return (
<div>
<ForwardChild ref={inputRef} />
<button onClick={getFocus}>获得焦点</button>
<p>{number}</p>
<button onClick={() => {
debugger
setNumber( number + 1)
}}>+</button>
</div>
)
}