认识 React Hooks 之一

今天要学习的是 React 的新功能 Hook,而 Hook 是 React 在 16.8 版本中新加入的功能,以往只要是需要管理到 state 的话,都一定要使用 class-based component 的方式才可以,而 Hook 的出现可以让我们也可以在 function component 中管理 state 以及使用 React 的功能。

而今天与明天的篇幅会学习关於使用 Hook 替代原本 class-based component 的写法。

接着就赶紧今天的学习吧!

使用 State Hook

第一个要介绍的是 useState这是一个能让我们将 state 加到 function component 中的 Hook。

相关测试范例,点击前往

在测试范例中提供了两种 component 的使用方式,这里则撷取出使用 useState 的程序码:

export default function App() {
  const [text, setText] = useState("Text from function component's state");

  const changeTextHandler = () => {
    setText("Text changed from function component's useState method");
  };

  return (
    <div className="App">
      <h2>function component 范例: 使用 useState</h2>
      <p>{text}</p>
      <button onClick={changeTextHandler}>变更文字</button>
    </div>
  );
}

当我们使用 useState 时,我们需要提供一个初始的值作为参数传入,而这也是 state 的初始值。

值得注意的是,比较 class-based component一定得传入一个物件, useState 接受像是 String 或者 Number 这类的值作为初始值。

useState 提供的部分,我们可以取得这个初始值以及用来更新这个值的方法

而命名上这里的 textsetText 是自定义的名称,只要记得顺序第一个值,而第二个则是用来更新的方法即可。

此外还需要注意的部分是 useStatethis.setState 更新时的差异

使用 this.setState 的时候,如果你只需要更新某一个值的时候,也许你会这麽写:

class App extends Components {
  state = {
    num: 0,
    text: 'Text'
  };
  
  componentDidMount() {
    this.setState({
      num: 666,
    });
  };
}

如果只需要更新 num 的时候,只需要把需要更新的写入即可,React 会自己 merge 到 class-based component 中的 state

但如果今天是透过 useState 的方式,那就需要这麽写:

const App = () => {
  const [num, setNum] = useState({
     num: 0,
    text: 'Text'
  })
  
  componentDidMount() {
    setNum({
      ...state,
      num: 666,
    });
  };
}

由於 useState 在更新时并不会 merge 需要更新的部分到原本的 state,而是整个 state 替换,所以如果没有将不需要更新的其他状态一并写入的话,就会造成状态的遗失,这点需要注意。

使用 Effect Hook

接着要学习的是 useEffect,至於为什麽会用 Effect 这个词呢?

原因在於像是 fetch 资料、订阅事件与改变 DOM,这些被称为是「side effect」的行为会影响其他 component 且在 render 期间无法完成的。

useEffect 基本上就是整合了 class-based component 的 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个生命周期,让这个 API 也可以达到同样的目的。

这里我们改写一下上一个测试范例,这次不透过点击切换 text 的值,而是在等同 class-based component 的生命周期 componentDidMount 的阶段,我们将 text 更新。

相关测试范例,点击前往

这里一样撷取出关於 useEffect 相关的测试程序码:

export default function App() {
  const [text, setText] = useState("Text from function component's state");

  useEffect(() => {
    setText("Text changed from function component's useState method");
  });

  return (
    <div className="App">
      <h2>
        function component 范例: 使用 useEffect 在相似於 componentDidMount
        的阶段更新 state
      </h2>
      <p>{text}</p>
    </div>
  );
}

当我们使用 useEffect 的时候,这代表我们告诉 React 在 component render 之後需要做一些事情,而 React 会在 render 之後执行我们传入的 function。

而将 useEffect 写在 component 的内部是因为这样可以透过闭包(closure)的观念取得 state 或者是 props 的状态。

这里需要注意的是目前的设定方式,预设会在每次 render 之後都一定会执行。

那要怎麽样才能在 text 的值有变化的时候才做执行就好呢?

这个时候我们就需要透过 useEffect 的第二个参数来达成

这里我们将上面的程序码改写一下:

export default function App() {
  const [text, setText] = useState("Text from function component's state");

  useEffect(() => {
    setText("Text changed from function component's useState method");
  }, [text]);

  return (
    <div className="App">
      <h2>
        function component 范例: 使用 useEffect 在相似於 componentDidMount
        的阶段更新 state
      </h2>
      <p>{text}</p>
    </div>
  );
}

每次 render 後就清除或执行 effect 可能会造成效能的问题,所以透过传入 useEffect 的第二个参数 [text]此时当重新 render 时, React 会比对该次的值是否与前一次的值,如果有不同才会执行 effect,否则就跳过这一次的更新

那如果是只想要在 componet mount 的阶段触发一次就好呢?

这时候也很简单,只要传入一个空阵列 [] 这样就好罗!

所以上方的程序码我们再改写一下:

export default function App() {
  const [text, setText] = useState("Text from function component's state");

  useEffect(() => {
    setText("Text changed from function component's useState method");
  }, []);

  return (
    <div className="App">
      <h2>
        function component 范例: 使用 useEffect 在相似於 componentDidMount
        的阶段更新 state
      </h2>
      <p>{text}</p>
    </div>
  );
}

而当有时候我们可能会在 component 中订阅一个事件,并且在这个元件被 unmount 的时候取消订阅这个事件,这时候应该怎麽做才好呢?

这个时候我们可以透过 return 一个 function 的方式达成这个目的

相关测试范例,点击前往

export default function App() {
  const [text, setText] = useState("初始文字");

  const test = () => {
    setText("有发现我不一样了吗?");
  };

  useEffect(() => {
    document.querySelector("h2").addEventListener("mouseenter", test);
    return () => {
      document.querySelector("h2").removeEventListener("mouseenter", test);
    };
  }, [text]);

  return (
    <div className="App">
      <h2>function component 范例: 请将滑鼠移到这个标题上,观察变化</h2>
      <p>{text}</p>
    </div>
  );
}

透过 return 一个 function 的方式可以让我们像是在 class-based component 中於生命周期 componentWillUnmount 的阶段时,取消对於事件的订阅。

而最後要提的部分是关於浏览器更新萤幕的部分,这边来看看文件是怎麽写的:

提示
与 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 安排的 effect 不会阻止浏览器更新萤幕。这使你的应用程序感觉起来响应更快。大多数 effect 不需要同步发生。在少见的需要同步发生的情况下(例如测量 layout),有另外一个 useLayoutEffect Hook,它的 API 与 useEffect 相同。

useContext

在第十五天的时候,我们学习了关於 Context API的使用方式,而在 component 中如果我们想要使用我们透过 Provider 传入的 Context 物件的时候,我们可以透过 Consumerstatic contextType = Context 的方式来取得。

而 React Hooks 对此也提供了更简便的方式让我们存取这个 Context 物件: 透过 useContext

这里我们改写第十五天的测试范例来看看怎麽使用 useContext 吧!

相关测试范例,点击前往

首先,我们一样需要建立一个 Context 物件:

// Context.js
import React from "react";

const Context = React.createContext({
  text: "",
  changeTextByContextAPI: () => {},
  changeTextByContextAPIInFuncComponent: () => {},
  changeTextByUseContextInFuncComponent: () => {}
});

export default Context;

接着,我们一样要透过 ProviderContext 物件提供给需要的 component:

// App.js
import React, { useState } from "react";
import "./styles.css";
import Card2 from "./components/Card2";
import Context from "./components/Context";
const App = () => {
  const [state, setTextState] = useState({
    contextText: "Initial value"
  });

  const changeTextByContextAPI = () => {
    setTextState({
      ...state,
      contextText: "change text by Context Provider/Consumer"
    });
  };

  return (
    <div className="App">
      <h2>透过 Context.Provider 提供 Coontext 物件的值到 card 元件中</h2>
      <p>此为 Class-based component</p>
      <Context.Provider
        value={{
          ...state,
          changeTextByContextAPI
        }}
      >
        <Card2 />
      </Context.Provider>
    </div>
  );
};

export default App;

最後我们需要透过 useContext 来取得这个 Context 物件:

// Card4.js

import React, { useContext } from "react";
import "./index.css";
import Context from "../Context";

const Card4 = () => {
  const context = useContext(Context);
  return (
    <div
      className="card"
      onClick={context.changeTextByUseContextInFuncComponent}
    >
      {context.contextTextInFuncComponentByUseContext}
    </div>
  );
};

export default Card4

没意外的话,应该成功取得,并执行目前测试范例上提供的操作罗!

今天学习了 React Hooks 中的 useStateuseEffect 以及 useContext

明天继续探索其他的 Hooks

铁人赛文章与程序码同步发布於:

  1. 个人部落格
  2. Github

资源


<<:  Python - pandas (v) dataframe资料框

>>:  Equal Sides Of An Array

DOM 是什麽 ? 先了解 Node & HTMLElement 就知道了

面试的时候很常会被问到 DOM 是什麽? 那 Node 和 HTMLElement 又是什麽呢? D...

Day 16 - 用 useReducer 取代 Redux !?

如果有错误,欢迎留言指教~ Q_Q useReducer 看起来跟 Redux 的 reducer...

IT铁人DAY 21-Facade 外观模式

  今天要介绍的模式是属於结构型模式的一种,我个人觉得他还蛮简单的,有点像是程序码中的主要窗口,现在...

JS Promise DAY77

常见的非同步问题(不限於 AJAX) 回呼地狱 写法不一致 无法同时执行(jQuery有并行语法,但...

Day 18:分离控制项的外观与行为

JUCE 因开发 DAW(Digital Audio Workstation)而生,十多年来持续发展...