FE-Interview icon indicating copy to clipboard operation
FE-Interview copied to clipboard

Day94:说一下React Hooks在平时开发中需要注意的问题和原因?

Open Genzhen opened this issue 5 years ago • 6 comments
trafficstars

Genzhen avatar Jul 09 '20 07:07 Genzhen

每日一题会在下午四点在交流群集中讨论,五点 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封装的状态管理工具。

Genzhen avatar Jul 09 '20 08:07 Genzhen

useContent 应该是 useContext

已修改哈,感谢提出错误

hitao123 avatar Jul 17 '20 08:07 hitao123

// 简单实现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];
}

GolderBrother avatar Jul 30 '20 14:07 GolderBrother

// 简单实现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不行吗

yaohuangguan avatar May 11 '21 12:05 yaohuangguan

// 简单实现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不会覆盖上一次的值吗

mesterLi avatar May 13 '21 03:05 mesterLi

useState 不会自动合并状态,并且不推荐直接用state来改变下次状态 使用hooks时要在顶部函数使用,这是为了方式hooks作用于条件语句内,因为更新前后hooks要保证一致,这样做的目的是因为react要进行状态的可持久化,在构建fiber树的过程中,要将老树的hooks 链表转移到新树上,顺序不一致就会出错

DaphnisLi avatar Feb 21 '23 13:02 DaphnisLi