首发于手不释卷
react hooks进阶与原理

react hooks进阶与原理

上一篇文章介绍了react hooks提出的原因、主要的useState,useContext,useEffect,以及如何开发自定义的hooks,接下来将进一步扩展。

useEffect/useLayoutEffect

  1. useEffect: 我们可以看到上面上一篇文章里面对useEffect的介绍:useEffect用于处理大多数副作用,其中的回调函数会在render执行之后在调用,确保不会阻止浏览器的渲染,这跟componentDidMount和componentDidUpdate是不一样的,他们会在渲染之后同步执行。
  2. useLayoutEffect: 在大多数情况下,我们都可以使用useEffect处理副作用,但是,如果副作用是跟DOM相关的,就需要使用useLayoutEffect。useLayoutEffect中的副作用会在DOM更新之后同步执行。

举个简单的useLayoutEffect的例子:

function App() {
    const [width, setWidth] = useState(0);
    useLayoutEffect(() => {
        const title = document.querySelector('#title');
        const titleWidth = title.getBoundingClientRect().width;
        if (width !== titleWidth) {
            setWidth(titleWidth);
        }
    });
    return <div>
        <h1 id="title">hello</h1>
        <h2>{width}</h2>
    </div>
}

useReducer

useReducer非常有用,能够替代一部分redux的功能。举个官方的例子吧:

const reducer = (state, action) => {
    switch(action.type) {
        case 'increment':
            return {count: state.count + 1};
        case 'decrement':
            return {count: state.count - 1};
        case 'reset':
            return initState;
        default:
            return state;
    }
};

function App() {
    const [state, dispatch] = useReducer(reducer, initState);
    return <div>
        <h1>{state.count}</h1>
        <button onClick={() => dispatch({type: 'increment'})}>+</button>
        <button onClick={() => dispatch({type: 'decrement'})}>-</button>
        <button onClick={() => dispatch({type: 'reset'})}>reset</button>
    </div>
}

useReducer接收两个参数,一个是reducer函数,跟redux中的reducer是一样的;另外一个是初始的状态值。返回的是一个数组,数组中的第一个元素是状态值,第二个元素是dispatch函数,你可以调用dispatch函数,来触发state的更新。

useRef

使用class组件,其中一个很重要的点事有时候我们需要获取某个组件或DOM节点的引用,借助useRef,我们可以在函数组件中获取组件或DOM节点的引用:

function App() {
    const inputRef = useRef(null);

    return <div>
        <input type="text" ref={inputRef}/>
        <button onClick={() => inputRef.current.focus()}>focus</button>
    </div>
}

上面这段代码,点击button出触发input的focus事件,非常简单。

React Hooks简单原理:只是数组

最开始接触到React Hooks的时候,觉得它十分的优秀,但又很神秘,最近看到一篇文章,简单的介绍了React Hooks的原理,逐渐解开了React Hooks神秘的面纱。

Hooks的使用有两个原则:

  1. 不要在循环,条件判断,函数嵌套中使用hooks
  2. 只能在函数组件中使用hooks

第二个原则很好理解,hooks的提出主要是为了解决class组件的一系列问题;但是第一个原则却让人感到困惑。

看一个使用useState的例子:

function RenderFunctionComponent() {
  const [firstName, setFirstName] = useState("Rudi");
  const [lastName, setLastName] = useState("Yardley");

  return (
    <Button onClick={() => setFirstName("Fred")}>Fred</Button>
  );
}

1、初始化

初始化的时候,会创建两个数组: state和setters,把光标的位置设为0.

2、第一次渲染

调用useState时,第一次渲染,会将一个set函数放入setters数组中,并且把初始state放入到state数组中.

3、后续渲染

每一次重新渲染,光标都会重新设为0,然后从对应的数组中读取状态和set函数

4 事件处理

每次调用set函数时,set函数将会修改state数组中对应的状态值,这种对应的关系是通过cursor光标来确定的

那篇文章的作者简单的实现了一下"简陋"的hooks:

let state = [];
let setters = [];
let firstRun = true;
let cursor = 0;

function createSetter(cursor) {
  return function setterWithCursor(newVal) {
    state[cursor] = newVal;
  };
}

// This is the pseudocode for the useState helper
export function useState(initVal) {
  if (firstRun) {
    state.push(initVal);
    setters.push(createSetter(cursor));
    firstRun = false;
  }

  const setter = setters[cursor];
  const value = state[cursor];

  cursor++;
  return [value, setter];
}

// Our component code that uses hooks
function RenderFunctionComponent() {
  const [firstName, setFirstName] = useState("Rudi"); // cursor: 0
  const [lastName, setLastName] = useState("Yardley"); // cursor: 1

  return (
    <div>
      <Button onClick={() => setFirstName("Richard")}>Richard</Button>
      <Button onClick={() => setFirstName("Fred")}>Fred</Button>
    </div>
  );
}

// This is sort of simulating Reacts rendering cycle
function MyComponent() {
  cursor = 0; // resetting the cursor
  return <RenderFunctionComponent />; // render
}

console.log(state); // Pre-render: []
MyComponent();
console.log(state); // First-render: ['Rudi', 'Yardley']
MyComponent();
console.log(state); // Subsequent-render: ['Rudi', 'Yardley']

// click the 'Fred' button

console.log(state); // After-click: ['Fred', 'Yardley']

看完简单的原理,我们来解释一下为什么不能在循环,条件判断,嵌套函数中使用hooks:

let firstRender = true;

function RenderFunctionComponent() {
  let initName;
  
  if(firstRender){
    [initName] = useState("Rudi");
    firstRender = false;
  }
  const [firstName, setFirstName] = useState(initName);
  const [lastName, setLastName] = useState("Yardley");

  return (
    <Button onClick={() => setFirstName("Fred")}>Fred</Button>
  );
}

这段代码在条件渲染中使用hooks,第一次渲染时,内部的两个数组映射关系如下:

我们可以看到firstName和setFirstName、lastName和setLastName有正确的对应关系,但是第二次渲染时:

firstName的值是initName,lastName的值是fistName,这种映射的关系就混乱了,所以这就是不要在循环,条件判断,嵌套函数中使用hooks的原因。



参考:

发布于 2018-12-02 15:32