FE-Interview
FE-Interview copied to clipboard
Day94:说一下React Hooks在平时开发中需要注意的问题和原因?
每日一题会在下午四点在交流群集中讨论,五点 Github、交流群同步更新答案
1)不要在循环,条件或嵌套函数中调用Hook,必须始终在React函数的顶层使用Hook
这是因为React需要利用调用顺序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易导致调用顺序的不一致性,从而产生难以预料到的后果。
2)使用useState时候,使用push,pop,splice等直接更改数组对象的坑
使用push直接更改数组无法获取到新值,应该采用析构方式,但是在class里面不会有这个问题
代码示例
function Indicatorfilter() {
let [num,setNums] = useState([0,1,2,3])
const test = () => {
// 这里坑是直接采用push去更新num
// setNums(num)是无法更新num的
// 必须使用num = [...num ,1]
num.push(1)
// num = [...num ,1]
setNums(num)
}
return (
<div className='filter'>
<div onClick={test}>测试</div>
<div>
{num.map((item,index) => (
<div key={index}>{item}</div>
))}
</div>
</div>
)
}
class Indicatorfilter extends React.Component<any,any>{
constructor(props:any){
super(props)
this.state = {
nums:[1,2,3]
}
this.test = this.test.bind(this)
}
test(){
// class采用同样的方式是没有问题的
this.state.nums.push(1)
this.setState({
nums: this.state.nums
})
}
render(){
let {nums} = this.state
return(
<div>
<div onClick={this.test}>测试</div>
<div>
{nums.map((item:any,index:number) => (
<div key={index}>{item}</div>
))}
</div>
</div>
)
}
}
3)useState设置状态的时候,只有第一次生效,后期需要更新状态,必须通过useEffect
看下面的例子
TableDeail是一个公共组件,在调用它的父组件里面,我们通过set改变columns的值,以为传递给TableDeail的columns是最新的值,所以tabColumn每次也是最新的值,但是实际tabColumn是最开始的值,不会随着columns的更新而更新
const TableDeail = ({
columns,
}:TableData) => {
const [tabColumn, setTabColumn] = useState(columns)
}
// 正确的做法是通过useEffect改变这个值
const TableDeail = ({
columns,
}:TableData) => {
const [tabColumn, setTabColumn] = useState(columns)
useEffect(() =>{setTabColumn(columns)},[columns])
}
4)善用useCallback
父组件传递给子组件事件句柄时,如果我们没有任何参数变动可能会选用useMemo。但是每一次父组件渲染子组件即使没变化也会跟着渲染一次。
5)不要滥用useContext
可以使用基于useContext封装的状态管理工具。
useContent 应该是 useContext
已修改哈,感谢提出错误
// 简单实现hooks
// 一、实现useState
const { render } = require("react-dom");
let memoriedStates = [];
let lastIndex = 0;
function useState(initialState) {
memoriedStates[lastIndex] = memoriedStates[lastIndex] || initialState;
function setState(newState) {
memoriedStates[lastIndex] = newState;
// 状态更新完毕,调用render函数。重新更新视图
render();
}
// 返回最新状态和更新函数,注意index要前进
return [memoriedStates[lastIndex++], setState];
}
// 二、实现useEffect
let lastDendencies; // 存放依赖项的数组
function useEffect(callback, dependencies) {
if (lastDendencies) {
// 判断传入的依赖项是不是都没有变化,只要有以一项改变,就需要执行callback
const isChange = dependencies && dependencies.some((dep, index) => dep !== lastDendencies[index]);
if (isChange) {
// 一开始没有值,需要更新一次(相当于componentDidMount)
typeof callback === 'function' && callback();
// 更新依赖项
lastDendencies = dependencies;
}
} else {
// 一开始没有值,需要更新一次(相当于componentDidMount)
typeof callback === 'function' && callback();
// 更新依赖项
lastDendencies = dependencies;
}
}
// 三、实现useCallback
let lastCallback; // 最新的回调函数
let lastCallbackDependencies = []; // 回调函数的依赖项
function useCallback(callback, dependencies = []) {
if (lastCallback) {
const isChange = dependencies && dependencies.some((dep, index) = dep !== lastCallbackDependencies[index]);
if (isChange) {
// 只要有一个依赖项改变了,就更新回调(重新创建)
lastCallback = callback;
lastCallbackDependencies = dependencies;
}
} else {
lastCallback = callback;
lastCallbackDependencies = dependencies;
}
// 最后需要返回最新的函数
return lastCallback;
}
// 四、实现useRef
let lastRef;
function useRef(initialValue = null){
lastRef = lastRef != undefined ? lastRef : initialValue;
// 本质上就是返回一个对象,对象种有一个current属性,值为初始化传入的值,如果没有传入初始值,则默认为null
return {
current: lastRef
}
}
// 五、实现useContext
function useContext(context){
// 很简单,就是返回context的_currentValue值
return context._currentValue;
}
// 六、实现useReducer
let lastState;
function useReducer(reducer, initialState){
lastState = lastState !== undefined ? lastState : initialState;
// dispatch一个action,内部就是自动调用reducer来计算新的值返回
function dispatch(action){
lastState = reducer(lastState, action);
// 更新完毕后,需要重新渲染视图
render();
}
// 最后返回一个的状态值和派发action的方法
return [lastState, dispatch];
}
// 简单实现hooks // 一、实现useState const { render } = require("react-dom"); let memoriedStates = []; let lastIndex = 0; function useState(initialState) { memoriedStates[lastIndex] = memoriedStates[lastIndex] || initialState; function setState(newState) { memoriedStates[lastIndex] = newState; // 状态更新完毕,调用render函数。重新更新视图 render(); } // 返回最新状态和更新函数,注意index要前进 return [memoriedStates[lastIndex++], setState]; } // 二、实现useEffect let lastDendencies; // 存放依赖项的数组 function useEffect(callback, dependencies) { if (lastDendencies) { // 判断传入的依赖项是不是都没有变化,只要有以一项改变,就需要执行callback const isChange = dependencies && dependencies.some((dep, index) => dep !== lastDendencies[index]); if (isChange) { // 一开始没有值,需要更新一次(相当于componentDidMount) typeof callback === 'function' && callback(); // 更新依赖项 lastDendencies = dependencies; } } else { // 一开始没有值,需要更新一次(相当于componentDidMount) typeof callback === 'function' && callback(); // 更新依赖项 lastDendencies = dependencies; } } // 三、实现useCallback let lastCallback; // 最新的回调函数 let lastCallbackDependencies = []; // 回调函数的依赖项 function useCallback(callback, dependencies = []) { if (lastCallback) { const isChange = dependencies && dependencies.some((dep, index) = dep !== lastCallbackDependencies[index]); if (isChange) { // 只要有一个依赖项改变了,就更新回调(重新创建) lastCallback = callback; lastCallbackDependencies = dependencies; } } else { lastCallback = callback; lastCallbackDependencies = dependencies; } // 最后需要返回最新的函数 return lastCallback; } // 四、实现useRef let lastRef; function useRef(initialValue = null){ lastRef = lastRef != undefined ? lastRef : initialValue; // 本质上就是返回一个对象,对象种有一个current属性,值为初始化传入的值,如果没有传入初始值,则默认为null return { current: lastRef } } // 五、实现useContext function useContext(context){ // 很简单,就是返回context的_currentValue值 return context._currentValue; } // 六、实现useReducer let lastState; function useReducer(reducer, initialState){ lastState = lastState !== undefined ? lastState : initialState; // dispatch一个action,内部就是自动调用reducer来计算新的值返回 function dispatch(action){ lastState = reducer(lastState, action); // 更新完毕后,需要重新渲染视图 render(); } // 最后返回一个的状态值和派发action的方法 return [lastState, dispatch]; }
1.useState, 为啥index要前进?0不行吗
// 简单实现hooks // 一、实现useState const { render } = require("react-dom"); let memoriedStates = []; let lastIndex = 0; function useState(initialState) { memoriedStates[lastIndex] = memoriedStates[lastIndex] || initialState; function setState(newState) { memoriedStates[lastIndex] = newState; // 状态更新完毕,调用render函数。重新更新视图 render(); } // 返回最新状态和更新函数,注意index要前进 return [memoriedStates[lastIndex++], setState]; } // 二、实现useEffect let lastDendencies; // 存放依赖项的数组 function useEffect(callback, dependencies) { if (lastDendencies) { // 判断传入的依赖项是不是都没有变化,只要有以一项改变,就需要执行callback const isChange = dependencies && dependencies.some((dep, index) => dep !== lastDendencies[index]); if (isChange) { // 一开始没有值,需要更新一次(相当于componentDidMount) typeof callback === 'function' && callback(); // 更新依赖项 lastDendencies = dependencies; } } else { // 一开始没有值,需要更新一次(相当于componentDidMount) typeof callback === 'function' && callback(); // 更新依赖项 lastDendencies = dependencies; } } // 三、实现useCallback let lastCallback; // 最新的回调函数 let lastCallbackDependencies = []; // 回调函数的依赖项 function useCallback(callback, dependencies = []) { if (lastCallback) { const isChange = dependencies && dependencies.some((dep, index) = dep !== lastCallbackDependencies[index]); if (isChange) { // 只要有一个依赖项改变了,就更新回调(重新创建) lastCallback = callback; lastCallbackDependencies = dependencies; } } else { lastCallback = callback; lastCallbackDependencies = dependencies; } // 最后需要返回最新的函数 return lastCallback; } // 四、实现useRef let lastRef; function useRef(initialValue = null){ lastRef = lastRef != undefined ? lastRef : initialValue; // 本质上就是返回一个对象,对象种有一个current属性,值为初始化传入的值,如果没有传入初始值,则默认为null return { current: lastRef } } // 五、实现useContext function useContext(context){ // 很简单,就是返回context的_currentValue值 return context._currentValue; } // 六、实现useReducer let lastState; function useReducer(reducer, initialState){ lastState = lastState !== undefined ? lastState : initialState; // dispatch一个action,内部就是自动调用reducer来计算新的值返回 function dispatch(action){ lastState = reducer(lastState, action); // 更新完毕后,需要重新渲染视图 render(); } // 最后返回一个的状态值和派发action的方法 return [lastState, dispatch]; }1.useState, 为啥index要前进?0不行吗 是为了下一个useState不会覆盖上一次的值吗
useState 不会自动合并状态,并且不推荐直接用state来改变下次状态 使用hooks时要在顶部函数使用,这是为了方式hooks作用于条件语句内,因为更新前后hooks要保证一致,这样做的目的是因为react要进行状态的可持久化,在构建fiber树的过程中,要将老树的hooks 链表转移到新树上,顺序不一致就会出错