React-Hooks 29分钟速成,时间就是金钱

2,735 阅读6分钟

不是说非要学这个Hooks,而是因为这个用起来真的很符合真香定理,没法不学啊~真的是谁用谁知道

正文开始:

React那么多生命周期的钩子函数到底该用哪个?组件还分有状态和无状态,那我想让函数组件也有自己的状态怎么办?难道函数组件就不配拥有状态吗?每次写个方法还得手动改变this指向?oh my god ! 有了Hooks,这些都再也不是问题了~

Hook顾名思义就是钩子的意思。在函数组件中把React的状态和生命周期等这些特性钩入进入,这就是React的Hook,那么 React Hooks 相比于从前的类组件到底有哪些好处呢?

  1. 代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过 React Hooks 可以将功能代码聚合,方便阅读维护
  2. 组件树层级变浅,在原本的代码中,我们经常使用 HOC/render props 等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,而在 React Hooks 中,这些功能都可以通过强大的自定义的 Hooks 来实现

useState 保存组件状态

在类组件中,我们使用 this.state 来保存组件状态,并对其修改触发组件重新渲染。比如下面这个简单的计数器组件,很好诠释了类组件如何运行:

import React from "react";
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  render() {
    const { count } = this.state;
    return (
      <div>
        Count: {count}
        <button onClick={() => this.setState({ count: count + 1 })}>+</button>
        <button onClick={() => this.setState({ count: count - 1 })}>-</button>
      </div>
    );
  }
}

一个简单的计数器组件就完成了,而在函数组件中,由于没有 this 这个黑魔法,React 通过 useState 来帮我们保存组件的状态

以前的函数式组件被成为纯函数组件或者无状态组件,是只能接受父组件传来的props并且只能做展示功能,不能使用state也没有生命周期。

现在State Hook 可以让函数式组件使用状态。

useState是React的一个Hook,它是一个方法,可以传入值作为state的默认值,返回一个数组,数组的第一项是对应的状态(默认值会赋予状态),数组的第二项是更新状态的函数

import React, { useState } from "react";
function App() {
  const [countObj, setCountObj] = useState({
    count: 0
  });
  return (
    <div className="App">
      Count: {obj.count}
      <button onClick={() => setCountObj({ ...countObj, count: countObj.count + 1 })}>+</button>
      <button onClick={() => setCountObj({ ...countObj, count: countObj.count - 1 })}>-</button>
    </div>
  );
}

有个 useState 后,函数组件也可以拥有自己的状态了

useEffect 处理副作用

函数组件能保存状态,但是却无法执行异步请求等副作用的操作,所以 React 提供了 useEffect 来帮助开发者处理函数组件的副作用操作

先看看原来的类组件是怎么处理的:

import React, { Component } from "react";
class App extends Component {
  state = {
    count: 1
  };
  componentDidMount() {
    const { count } = this.state;
    document.title = "componentDidMount" + count;
    this.timer = setInterval(() => {
      this.setState(({ count }) => ({
        count: count + 1
      }));
    }, 1000);
  }
  componentDidUpdate() {
    const { count } = this.state;
    document.title = "componentDidMount" + count;
  }
  componentWillUnmount() {
    document.title = "componentWillUnmount";
    clearInterval(this.timer);
  }
  render() {
    const { count } = this.state;
    return (
      <div>
        Count:{count}
        <button onClick={() => clearInterval(this.timer)}>clear</button>
      </div>
    );
  }
}

我们写的有状态组件,通常会产生很多的副作用(side effect),比如发起ajax请求获取数据,添加一些监听的注册和取消注册,手动修改dom等等。我们之前都把这些副作用的函数写在生命周期函数钩子里,比如componentDidMount,componentDidUpdate和componentWillUnmount。而现在的useEffect就相当与这些声明周期函数钩子的集合体。它以一抵三。

同时,由于前文所说hooks可以反复多次使用,相互独立。所以我们合理的做法是,给每一个副作用一个单独的useEffect钩子。这样一来,这些副作用不再一股脑堆在生命周期钩子里,代码变得更加清晰

在上边的例子中,组件每隔一秒更新组件状态,并且每次触发更新都会触发 document.title 的更新(副作用),而在组件卸载时修改 document.title,接下来我们用useEffect来实实现它。

import React, { useState, useEffect } from "react";
let timer = null;
function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = "componentDidMount" + count;
  },[count]);

  useEffect(() => {
    timer = setInterval(() => {
      setCount(prevCount => prevCount + 1);
    }, 1000);
    return () => {
      document.title = "componentWillUnmount";
      clearInterval(timer);
    };
  }, []);
  return (
    <div>
      Count: {count}
      <button onClick={() => clearInterval(timer)}>clear</button>
    </div>
  );
}

我们使用 useEffect 重写了上面的例子,useEffect 第一个参数传递函数,可以用来做一些副作用比如异步请求,修改外部参数等行为,而第二个参数是个数组,如果数组中的值才会触发 useEffect 第一个参数中的函数。返回值(如果有)则在组件销毁或者调用函数前调用。

  1. 比如第一个 useEffect 中,理解起来就是一旦 count 值发生改变,则修改 documen.title 值
  2. 而第二个 useEffect 中数组没有传值,代表不监听任何参数变化,即只有在组件初始化或销毁的时候才会触发,用来代替 componentDidMount 和 componentWillUnmount;

useContext 祖孙传值

useContext 是 React 帮你封装好的,用来处理多层级传递数据的方式,在以前组件树种,跨层级祖先组件想要给孙子组件传递数据的时候,除了一层层 props 往下透传之外,还可以使用useContext来解决爷孙组件的传值问题,新的Context API使用订阅发布者模式方式实现在爷孙组件中传值

const { Provider, Consumer } = React.createContext(null);
function Bar() {
  return <Consumer>{value => <div>{value}</div>}</Consumer>;
}
function Foo() {
  return <Bar />;
}
function App() {
  return (
    <Provider value={"hello context"}>
      <Foo />
    </Provider>
  );
}

通过 React createContext 的语法,在 APP 组件中可以跨过 Foo 组件给 Bar 传递数据。而在 React Hooks 中,我们可以使用 useContext 进行改造。

const Context = React.createContext("hello");
function Bar() {
  const val = useContext(Context); // 使用useContext直接取值
  return <div>{val}</div>;
}
function Foo() {
  return <Bar />;
}
function App() {
  return (
    <Context.Provider value={"hello react"}>
      <Foo />
    </Context.Provider>
  );
}

传递给 useContext 的是 context 而不是 consumer,返回值即是想要透传的数据了。用法很简单,使用 useContext 可以解决 Consumer 多状态嵌套的问题。

而使用 useContext 则变得十分简洁,可读性更强且不会增加组件树深度。

useReducer

看到useReducer,肯定会想到Redux,没错它和Redux的工作方式是一样的。useReducer的出现是useState的替代方案,能够让我们更好的管理状态,但是缺点也是存在的,就是无法实现redux的中间件

import React, { useReducer } from "react";
const initialState = {
  count: 0
};
function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + action.payload };
    case "decrement":
      return { count: state.count - action.payload };
    default:
      throw new Error();
  }
}
function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "increment", payload: 5 })}>
        +
      </button>
      <button onClick={() => dispatch({ type: "decrement", payload: 5 })}>
        -
      </button>
    </>
  );
}

用法跟 Redux 基本上是一致的,如果你学过redux,对于useReducer基本没有学习门槛

总结

React Hooks说到底绝对是一种趋势,因为Hooks用起来实在是让人欲罢不能,不需要考虑那么多,用起来更加的方便快捷,更像是回归了js的本质,也不会增加嵌套层级,何乐而不为呢?