今天要学习的是 React 的新功能 Hook,而 Hook 是 React 在 16.8 版本中新加入的功能,以往只要是需要管理到 state 的话,都一定要使用 class-based component 的方式才可以,而 Hook 的出现可以让我们也可以在 function component 中管理 state 以及使用 React 的功能。
而今天与明天的篇幅会学习关於使用 Hook 替代原本 class-based component 的写法。
接着就赶紧今天的学习吧!
第一个要介绍的是 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
提供的部分,我们可以取得这个初始值以及用来更新这个值的方法
而命名上这里的 text
与 setText
是自定义的名称,只要记得顺序第一个值,而第二个则是用来更新的方法即可。
此外还需要注意的部分是 useState
与 this.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
替换,所以如果没有将不需要更新的其他状态一并写入的话,就会造成状态的遗失,这点需要注意。
接着要学习的是 useEffect
,至於为什麽会用 Effect 这个词呢?
原因在於像是 fetch 资料、订阅事件与改变 DOM,这些被称为是「side effect」的行为会影响其他 component 且在 render 期间无法完成的。
而 useEffect
基本上就是整合了 class-based component 的 componentDidMount
、componentDidUpdate
与 componentWillUnmount
这三个生命周期,让这个 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
物件的时候,我们可以透过 Consumer
或 static 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;
接着,我们一样要透过 Provider
将 Context
物件提供给需要的 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 中的 useState
、useEffect
以及 useContext
明天继续探索其他的 Hooks
铁人赛文章与程序码同步发布於:
<<: Python - pandas (v) dataframe资料框
面试的时候很常会被问到 DOM 是什麽? 那 Node 和 HTMLElement 又是什麽呢? D...
如果有错误,欢迎留言指教~ Q_Q useReducer 看起来跟 Redux 的 reducer...
今天要介绍的模式是属於结构型模式的一种,我个人觉得他还蛮简单的,有点像是程序码中的主要窗口,现在...
常见的非同步问题(不限於 AJAX) 回呼地狱 写法不一致 无法同时执行(jQuery有并行语法,但...
JUCE 因开发 DAW(Digital Audio Workstation)而生,十多年来持续发展...