callcc.dev

以下是我自己对 React Hooks 的理解,具体详情可以查看官方文档,十分清晰。

Δ为什么需要它?

我们先来看看现在的 class 形式有什么问题。一个例子:
class MyComponent extends React.Component<{ id: number }> {
  constructor(props) {
    super(props);
    this.state = { name: "" };
    this.getNameByID = this.getNameByID.bind(this);
  }

  componentDidMount() {
    this.getNameByID();
  }

  getNameByID() {
    const names = ["Alice", "Bob", "Carol"];
    const name = id >= 0 && id < names.length ? names[id] : "Oops";
    this.setState({ name });
  }

  render() {
    return <div>{this.state.name}</div>;
  }
}
我们会发现使用 class 形式去写组件会很繁琐,每增加一个方法都要在构造方法里绑定一下this,而且必须调用super(props),十分难受。同时我们的状态处理逻辑和当前的MyComponent绑定在了一起,如果我们想在其他组件里复用这套逻辑,那么就会需要用到高阶组件(HOC),但是 HOC 会很容易碰到嵌套地狱的问题,这点其实在传递 props 的时候就可以体会得到了。把状态处理逻辑和 UI 组件耦合在一起不是一个很好的处理方式,我们需要一个技术,能把状态处理逻辑抽象出来,而 React Hooks 正是这样的一个存在。
React Hooks 这个东西,让我很容易联想到 AOP(Aspect Oriented Programming)。以往需要进行垂直注入的逻辑(传递参数),现在我们可以从水平注入。那么我们来看看如何使用 React Hooks 来水平注入我们的状态处理逻辑。
我们用 React Hooks 重写上面的组件,之后你会发现,使用 React Hooks 是多么愉悦的一件事情,你不再需要记住组件的生命周期。

Δ使用 React Hooks 重写

然后我们组件可以这样写
function MyComponent(props: { id: number }) {
  const [name, setName] = useState("");
  useEffect(() => {
    const names = ["Alice", "Bob", "Carol"];
    const name = id >= 0 && id < names.length ? names[id] : "Oops";
    setName(name);
  }, []);

  return <div>{name}</div>;
}
一模一样的效果,代码更简洁了有没有!我们先来扩展一个功能,一般来说,如果 id 变了,那么展示的 name 应该重新获取。如果我们是用 class 形式去写组件,那么首先要想的是在哪个生命周期函数里可以监测到 props 变化,然后更新其他状态,类似 Vue 的 watch。而如果我们使用 React Hooks,那么我们完全可以忘记这些东西,仅仅修改一行代码即可。
function MyComponent(props: { id: number }) {
  const [name, setName] = useState("");
  useEffect(() => {
    const names = ["Alice", "Bob", "Carol"];
    const name = id >= 0 && id < names.length ? names[id] : "Oops";
    setName(name);
  }, [id]);

  return <div>{name}</div>;
}
仅仅需要把 id 放入useEffect第二个参数列表里,每当 id 更新之后,就会执行方法块,重新设置 name,也就会重新渲染。对于useEffect的第二个参数有三种用法,第一个是不赋值,那么相当于componentDidMount+ComponentDidUpdate;给定一个空列表,相当于componentDidMount;给定不为空的列表,那么就相当于在componentDidMount+ComponentDidUpdate里判断属性有没有变化。可以看到,使用 React Hooks 会把状态管理更颗粒化,不会出现在生命周期函数里对很多状态进行判断然后再执行对应的逻辑。

Δ抽象状态处理逻辑

好了,现在说我们在另外一个组件里也需要使用这套根据 id 获取名字的逻辑。一个方法是拷贝代码,当然这是一个十分不推荐的行为,违反了 DRY 原则。又或者把这套逻辑抽象出来之后以参数形式注入到组件里。而使用 React Hooks 则可以很轻松完成这个任务,这时候就体现出了水平注入。
function useName(id: number) {
  const [name, setName] = useState("");
  useEffect(() => {
    const names = ["Alice", "Bob", "Carol"];
    const name = id >= 0 && id < names.length ? names[id] : "Oops";
    setName(name);
  }, [id]);

  return [name];
}
// import useName
function MyComponent(props: { id: number }) {
  const [name] = useName(id);

  return <div>{name}</div>;
}
你需要多少个组件都没问题
// import useName
function FunnyHooks(props: { id: number }) {
  const [name] = useName(id);

  return <span>{name}</span>;
}

Δ注意点

需要注意的是,因为 useEffect 里使用的比较方法是Object.is,所以,如果你是监测一个嵌套的对象,那么必须要重新生成,只修改内部的属性是不会触发这个 useEffect 的。方法很简单
nested.prop1 = "new value";
setNested(Object.assign({}, nested));

ΔOne more thing

这里只涉及到里useStateuseEffect,官方还有更多的 use*,GitHub 上也有一个库 react-use,里面封装了许多 Hooks。