云樾
踏浪而来
云樾
探究React Hooks
探究React Hooks

1. Hook的定义

React Hooks 设计的目的:加强版函数组件,让函数组件也拥有类组件的功能

"Hook"的意思是钩子

React Hooks想要达到的效果就是在尽量使用纯函数,且如需要外部功能副作用,就用Hooks将它们“钩”进来

React默认提供了一些常用的钩子,钩子一律使用use前缀命名,常用的钩子如下:

  • useState()
  • useReducer()
  • useContext()
  • useEffect()

2. Hook:useState

State Hook使用的例子:

const MyCount = () => {
  // 声明了count这个state变量
  const [count, setCount] = useState(0); // 数组解构

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

调用useState时React为我们定义了一个state变量,我们将这个变量命名为countuseState还会返回一个更新state的函数,同样我们将之命名为setCount

在函数组件中,函数退出后变量就会消失,而React就通过useState替我们保存了变量,让函数也拥有保存state的能力

问题:React是怎么知道useState对应的是哪个函数组件?

3. Hook:useEffect

Effect Hook可以让你在函数组件中执行副作用操作,这里的副作用有点像生命周期

可以把 useEffect Hook 看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。

3.1 无需清除的effect操作

在使用React class中,我们通常将副作用放在componentDidMountcomponentDidUpdate 中,如下面计时器实例:

class MyCountClass extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  componentDidMount() {
    document.title = You clicked ${this.state.count} times;
  }

  componentDidUpdate() {
    document.title = You clicked ${this.state.count} times;
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>Click me</button>
      </div>
    );
  }
}

我们需要在两个生命周期函数中编写重复的代码,因为我们希望组件在初次加载和更新时执行同样的操作

而我们使用Hook Effect,如下:

const MyCountHook = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = You clicked ${count} times;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count => count + 1)}>Click me</button>
    </div>
  );
};

通过使用useEffect这个Hook,我们可以告诉React组件在渲染后执行哪些操作,React会在Dom更新后执行我们传入的函数(这个函数称之为effect

useEffect首次渲染后和每次更新后都会执行,我们可以简单理解为effect发生在渲染之后,React保证每次运行effect时,Dom都已经更新完毕。

effect中我们可以获取到最新的count值,所以传给useEffect Hook的函数(effect)在每次渲染中都是不同的;每次重新渲染都会生成新的effect——每个effect都属于某一次特定的渲染。

componentDidMountcomponentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕;使用useLayoutEffect则会在Dom更新完毕前调度effect,会触发页面阻塞。

3.2 需要清除的effect操作

有一些副作用是需要清除的(如clearInterval),这种情况下清除工作可以防止内存泄露。

如下面的例子:

const MyCountHook = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    // 清除interval的"副作用"
    return () => {
      clearInterval(interval);
    };
  });

  return (
    <div>
      <p>Now, the count is {count}</p>
    </div>
  );
};

effect返回函数effect中可选的清除机制,这样可以有效地将添加副作用与清除工作绑定在一起。

React会在组件卸载时执行effect的清除操作,effect在每次渲染时都会执行,所以React在执行当前effect时会对上一个effect进行清除。

3.3 useEffect的第二个参数

useEffect(() => {...}, [count]);

第二个参数传入一个数组,只有数组中的“state变量”发生变化时,传入的effect才会被执行。

当第二个参数为空数组([])时,传入的effect只有在第一次渲染时会执行,之后函数组件更新时均不会执行,当然也不会有effect的卸载。

4. Hook:useReducer

useState的替代方案,它接收形如(state, action) => newState的reducer,并返回当前最新的state,以及配套的工作方法dispatch,用法与redux类似。

下面是使用useReducer的计数器例子:

// 初始化状态值
const initialState = {
  count: 0,
};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

const MyCountHook = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  useEffect(() => {
    const interval = setInterval(() => {
      dispatch({ type: 'increment' });
    }, 1000);
    return () => {
      clearInterval(interval);
    };
  });

  return (
    <div>
      <p>Now, the count is {state.count}</p>
    </div>
  );
};

5. Hook:useRef

const refContainer = useRef(initialValue);

useRef返回一个可变的ref对象,其.current属性被初始化为传入的参数(initialValue)。返回的ref对象在组件的整个生命周期内保持不变。

const MyCountHook = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  const [name, setName] = useState('xuanxiao');
  const inputEl = useRef(null);

  useEffect(() => {
    console.log(inputEl); // {current: input}
    console.log('effect invoked');
    return () => console.log('effect detached');
  }, [state.count, name]);

  return (
    <div>
      <input ref={inputEl} value={name} onChange={e => setName(e.target.value)}></input>
      <button onClick={() => dispatch({ type: 'increment' })}>{state.count}</button>
    </div>
  );
};

发表评论

textsms
account_circle
email

云樾

探究React Hooks
1. Hook的定义 React Hooks 设计的目的:加强版函数组件,让函数组件也拥有类组件的功能 "Hook"的意思是钩子 React Hooks想要达到的效果就是在尽量使用纯函数,且如需要外部功…
扫描二维码继续阅读
2019-09-26