React hook16.8新增特性,不能在class组件中使用
只能在函数组件的最外层调用hook,不要在循环、条件判断或子函数中调用
useState
通过在函数组件里调用它来给组件添加一些内部 state。React 会在重复渲染时保留这个 state。useState
会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState
,但是它不会把新的 state 和旧的 state 进行合并。
看下面两段代码:
const [count, setCount] = useState(1);
const handleClick = () => {
for (let i = 0; i < 3; i++) {
setCount(count + 1);
}
};
点击button, count值只增加了1,这里可以看出这样修改状态是异步的,执行并没有立即生效,每次拿到的值都是初始值
const [count, setCount] = useState(1);
const handleClick = () => {
for (let i = 0; i < 3; i++) {
setCount((pre) => {
return pre + 1;
});
}
};
点击button,但这时count值增加了3,这里方法中的参数,是把上一次调用setCount方法得到的值回传回来,这样就能保证每次都+1了。
另外,useState的初始值需要经过比较复杂的方法计算得到建议写成如下方式:
const [count, setCount] = useState(() => {
return computedCount();//这个计算很复杂
});
如果写成如下方式
const initCount = computedCount(); //这个计算很复杂
const [count, setCount] = useState(initCount);
则每次需要都要执行这个复杂计算,影响渲染
useEffect
useEffect有两个参数,useEffect(function, array?) : 第一个参数是一个方法,第二个参数可选,为数组类型。当第二个参数不填时,每次渲染都会执行第一个参数的方法;当第二个参数设置为[]空数组时,只有当组件第一次挂载时,才会执行第一个参数的方法;当第二个参数数组有值,例如[a,b],只有当a和b发生变化时,才会执行第一个参数的方法。
其中第一个参数function可以有返回值,返回值是也是一个function
例如:
useEffect(() => {
//add listener
return () => {
//remove listener
};
}, []);
当第二个参数数组为空时,返回值的function 就是在这个组件卸载时被执行。这个一般用来在挂载的时候注册一些监听,卸载时,移除监听。
useEffect(() => {
let timer = setInterval(() => {}, time);
return () => {
clearInterval(timer);
};
}, [time]);
当第二个参数数组不为空时,例如[a, b] , 就是在a,b发生变化时,先执行返回值的function,然后再执行第一个参数的方法。这里就是开启一个定时器,当time发生变化时,先停止掉上一个定时器,然后再开启一个新的定时器。
useLayoutEffect使用方法跟useEffect基本一致,但是:
1、useEffect是在dom渲染到页面后才回去执行,useLayoutEffect是在dom渲染到页面之前执行的
2、如果有操作dom的逻辑尽量放在useLayoutEffect中,因为如果放在useEffect中的话会看到闪烁
3、useEffect能覆盖大部分场景,因为useLayoutEffect会阻塞渲染,需要小心使用
看看useLayout官方的解释:
useContext
避免一层层传递props,也可以通过redux、mbox等状态管理来解决(正常项目中写法是分开多个文件)
const TestContext = React.createContext({ name: 'zhangsan', age: 20 });
const Test = () => {
return (
<TestContext.Provider value={{ name: 'lisi', age: 18 }}>
<Child />
</TestContext.Provider>
);
};
const Child = React.memo(() => {
return <Grand />;
});
const Grand = React.memo(() => {
const context = useContext(TestContext);
return (
<div>
<p>{`${context.name}-${context.age}`}</p>
</div>
);
});
export default Test;
useReducer
也是用来实现状态管理的hook,useState就是基于useReducer实现的,useReducer可以实现比useState更复杂的状态管理逻辑
function reducer(state, action) {
switch (action.type) {
case 'changeName':
return { ...state, name: action.value };
case 'changeAge':
return { ...state, age: action.value };
default:
return state;
}
}
const initData = { name: 'zhangsan', age: 20 };
const Test = () => {
const [person, dispatchPerson] = useReducer(reducer, initData);
return (
<>
<p>{`${person.name} - ${person.age}`}</p>
<button
onClick={() => dispatchPerson({ type: 'changeName', value: 'lisi' })}
>
change name
</button>
<button onClick={() => dispatchPerson({ type: 'changeAge', value: 18 })}>
change age
</button>
</>
);
};
export default Test;
useCallback
可以传入两个参数,第一个参数是方法,第2个参数是依赖关系,也是可选参数,返回值是第一个参数的方法,有记忆功能。只有在依赖项发生变化时,返回值才会发生变化,如果不传参数,则 每次组件渲染都会返回新的方法。
这样可以减少子组件不必要的刷新,但是第一个参数依然是会有开销,所以不用所有的方法都用useCallback包裹起来
如下代码:当count2发生变化时,addFunc没有变化,child这个子组件就不会重新绘制了。
const Test = () => {
const [count, setCount] = useState(0);
const [count2, setCount2] = useState(1);
const addFunc = useCallback(() => {
setCount(count + 1);
}, [count]);
const doubleFunc = () => {
setCount2(count2 * 2);
};
return (
<>
<p>{`${count}-${count2}`}</p>
<Child add={addFunc} />
<button onClick={doubleFunc}>double</button>
</>
);
};
const Child = React.memo((props: { add: any }) => {
console.log('child load');
return <button onClick={props.add}>add</button>;
});
export default Test;
useMemo
可以传入两个参数,第一个参数是方法,用来进行一些计算,并将结果返回,第2个参数是依赖关系,也是可选参数,返回值是第一个参数的方法return出去的值,只有在依赖项发生变化时,才会重新执行第一个参数的方法,如果不传参数,则 每次组件渲染都会重新计算。
useMemo是针对于当前组件高开销的计算的优化 ,具有记忆功能,假如页面上有一个比较大的高开销的计算,每次set一个值的时候,都会重新执行计算函数,这样的话,很占内存,这时候可以用到useMemo
// 假设computedNum()是一个计算量非常大的方法
const computeValue = useMemo(() => computedNum(num), [num]);
useCallback 对于子组件渲染优化
useMemo 对于当前组件高开销的计算优化
useRef
1、可以帮助我们获取dom和react组件实例
2、也可以用来存储变量,但是页面不会重新render
forwardRef可以在父组件中操作子组件的ref对象,并且将ref对象作为一个参数传递给子组件
具体用法如下:
const Test = () => {
const inputRef = useRef<HTMLInputElement>(null);
const childRef = useRef<HTMLInputElement>(null);
const handleFocus = () => {
inputRef?.current?.focus();
};
const handleChildFocus = () => {
childRef?.current?.focus();
};
return (
<>
<input ref={inputRef} />
<button onClick={handleFocus}>Focus current input</button>
<ChildForward ref={childRef} />
<button onClick={handleChildFocus}>Focus child input</button>
</>
);
};
function Child(props, ref) {
return <input ref={ref} />;
}
const ChildForward = forwardRef(Child);
export default Test;
useImperativeHandle
父组件可以调用子组件中的方法
const Test = () => {
const childRef: any = useRef();
const handleAdd = () => {
childRef?.current?.addFunc();
};
return (
<>
<button onClick={handleAdd}>add</button>
<Child onRef={childRef} />
</>
);
};
const Child = React.memo((props: { onRef: any }) => {
const [count, setCount] = useState(0);
const add = () => {
setCount(count + 1);
};
useImperativeHandle(
props.onRef,
() => {
return {
addFunc: add,
};
},
[count]
);
return <p>{count}</p>;
});
自定义hook,可以看看官方文档,讲的很清楚。
import React, { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
......
const isOnline = useFriendStatus(props.friend.id);
......