React v16.8
發佈了 Hooks
,其主要是解決跨組件、組件複用的狀態管理問題。react
在 class
中組件的狀態封裝在對象中,而後經過單向數據流來組織組件間的狀態交互。這種模式下,跨組件的狀態管理變得很是困難,複用的組件也會由於要兼容不一樣的組件變得產生不少反作用,若是對組件再次拆分,也會形成冗餘代碼增多,和組件過多帶來的問題。git
後來有了 Redux
之類的狀態管理庫,來統一管理組件狀態。可是這種分層依然會讓代碼變得很複雜,須要更多的嵌套、狀態和方法,寫代碼時也常在幾個文件之間不停切換。hooks
就是爲了解決以上這些問題。github
文章不對 hooks
作太多詳細介紹,建議閱讀此文前,先到官網作大概的瞭解。此文基於上一篇文章《實現ssr服務端渲染》的代碼,進行 hooks
改造。代碼已經提交到倉庫的 hooks
分支中,倉庫連接 github.com/zimv/react-…。編程
在瞭解 hooks
的過程當中,慢慢的感受到了面向對象和函數式編程的區別。api
在 class
模式中狀態和屬性方法等被封裝在組件內,組件之間是相互以完整對象個體作交互,狀態的修改須要在對象內部的 setState 中處理。數組
而 hooks
模式中,一切皆函數,也就是 hooks
,能夠被拆分紅不少小單元再進行組合,修改狀態的是一個 set
方法,此方法能夠在任何其餘的 hooks
中出現和調用。 class 更屬於面向對象編程,而 hooks
更屬於函數式編程。bash
React
也並不會移除 class
,而是引入 hooks
使開發者能根據場景作更好的選擇。它們依舊會在將來保持應有的迭代。異步
使用 hooks
以後,本來的生命週期概念就會有所變化了。好比咱們定義一個 hooks
組件 Index, 當組件運行時,Index
函數的調用就是一次 render
,那麼咱們第一次 render
至關於原來的 willMount
,而 useEffect
會在第一次 render
之後執行。官網文檔也說過你能夠把 useEffect
Hooks
視做 componentDidMount
、componentDidUpdate
和 componentWillUnmount
的結合。 state
也被 useState
替代,useState
傳入初始值並返回變量和修改變量的 set
方法。async
在咱們服務端渲染的時候,上篇文章說過生命週期只會執行到 willMount
後的第一次 render
。 那在咱們 hooks
模式下,服務端渲染會執行 Index hooks
第一次 render
,而 useEffect
不會被執行。函數式編程
function Index(props){
console.log('render');
const [desc, setDesc] = useState("惹不起");
useEffect(() => {
console.log('effect')
})
return (<div>{desc}</div>)
}複製代碼
若是使用了 useEffect
,組件鉤子每次 render
之後,useEffect
會被執行。 useEffect
第一個入參是須要調用的方法,方法能夠返回一個方法,返回的方法會在非首次執行此 useEffect
以前調用,也會在組件卸載時調用。
第二個參數是傳入一個數組,是用來限制 useEffect
執行次數的,若是不傳入此參數,useEffect
會在每次 render
時執行。若是傳入第二個數組參數,在非首次執行 useEffect
時,數組中的變量較上一次 render
發生了變化,纔會再次觸發 useEffect
執行。
看以下代碼,當頁面首次 render
,useEffect
執行異步數據獲取,當數據獲取成功,setList
設置值之後(相似 setState
會觸發 render
),會再次執行 render
,而 useEffect
還會再次執行,數據請求結束之後,setList
又會致使 render
,所以陷入死循環。
const [list, setList] = useState([]);
useEffect(() => {
API.getData().then(data=>{
if (data) {
setList(data.list);
}
});
});複製代碼
因此須要使用第二個參數,限制執行次數,咱們傳入一個 1
,就能夠實現僅執行一次 useEffect
。固然也能夠經過傳入一個 useState
變量。
const [list, setList] = useState([]);
useEffect(() => {
API.getData().then(data=>{
if (data) {
setList(data.list);
}
});
}, [1]);複製代碼
在本來的 SSR 倉庫的前提下,僅針對組件部分,進行 hooks
改造。首先回顧 getInitialProps
在 class
模式下,是在 class
寫一個 static
靜態方法,以下:
export default class Index extends Base {
static async getInitialProps() {
let data;
const res = await request.get("/api/getData");
if (!res.errCode) data = res.data;
return {
data
};
}
}複製代碼
在 hooks
中,class
變成了普通函數,之前的繼承變得沒有必要也沒法適應需求,所以 getInitialProps
直接寫在函數的屬性中,方法自己返回的數據格式依然不變,返回一個對象。以下:
function Index(props) {
}
Index.getInitialProps = async () => {
let data;
const res = await request.get("/api/getData");
if (!res.errCode) data = res.data;
return {
data
};
};複製代碼
包括定義的網頁 title
,以前也是使用 static
,如今咱們也 Index.title = 'index'
這樣定義。
hooks
規範要求以下,援引中文 React
文檔:
在 class
模式下,咱們繼承了 Base
,Base
會定義 constructor
和 componentWillMount
來處理 state
和 props
,能夠幫助咱們解決服務端渲染和客戶端渲染下初始化狀態數據的賦值和獲取,所以咱們才能夠統一一套代碼在客戶端和服務端中運行(如須要,查看上篇文章瞭解詳情)。
class
模式下的繼承 Base
屬於面向對象編程模式,而 hooks
模式下,因爲須要在函數內使用 useState
來定義狀態,而且返回方法來設置狀態,這樣看起來更偏向函數式編程,在這種場景下,繼承變得不適應。所以須要對 Base
進行改造,在 Base
編寫 hooks
,在頁面組件 hooks
中使用。
在 class
模式下,咱們使用繼承 Base
來處理 state
和 props
,因爲 Base
已經封裝了 constructor
和 componentWillMount
處理 state
和 props
,所以咱們只須要定義好靜態 state
和 getInitialProps
,組件便會自動處理相關邏輯,大體使用代碼以下
export default class Index extends Base {
static state = {
desc: "Hello world~"
};
static async getInitialProps() {
let data;
const res = await request.get("/api/getData");
if (!res.errCode) data = res.data;
return {
data
};
}
}複製代碼
在 hooks
模式下不同,由於摒棄了繼承,須要用 Base
自定義 hooks
,而後在頁面組件中使用。Base
中的 getProps
和 requestInitialData
鉤子調用時,須要傳入當前 Index
組件的部分對象,而後在 Base hooks
中返回變量初始值值或者調用 set
修改當前 hooks
中的狀態值,大體使用以下:
import { getProps, requestInitialData } from "../base";
function Index(props) {
const [desc, setDesc] = useState("Hello world~");
//getProps獲取props中的ssrData,重構和服務端渲染時props有值,第三個參數爲默認值
const [data, setData] = useState(getProps(props, "data", ""));
//在單頁面路由頁面跳轉,渲染組件時,requestInitialData調用getInitialProps
requestInitialData(props, Index, { data: setData });
return (<div>{data}</div>)
}
Index.getInitialProps = async () => {
let data;
const res = await request.get("/api/getData");
if (!res.errCode) data = res.data;
return {
data
};
};
export default Index;複製代碼
如此封裝之後,咱們依然保證了一套代碼能在服務端和客戶端運行,requestInitialData
方法第三個傳入參數,是一個對象,傳入了須要被修改的狀態的 set
方法,最終 getInitialProps
返回數據後,會和傳入的對象對比,屬性名一致便會調用 set
方法進行狀態修改,requestInitialData
是一個 useEffect hook
,代碼以下
export function requestInitialData(props, component, setFunctions) {
useEffect(() => {
//客戶端運行時
if (typeof window != "undefined") {
//非同構時,而且getInitialProps存在
if (!props.ssrData && component.getInitialProps) {
component.getInitialProps().then(data => {
if (data) {
//遍歷結果,執行set賦值
for (let key in setFunctions) {
for (let dataKey in data) {
if (key == dataKey) {
setFunctions[key](data[dataKey]);
break;
}
}
}
}
});
}
}
},[1]);
}複製代碼
至此,針對我以前的 SSR 代碼,就完成了 hooks
的改造。React hooks
的改造很是平滑,class
和 hooks
混用也不會形成什麼問題,若是須要在舊的項目中使用 hooks
或者對原有的 class
進行改造,徹底能夠慢慢的一部分一部分迭代。固然 React Hooks
還有 useContext
useReducer
等,不妨如今就去試試 Hooks ?
關聯文章:《 實現ssr服務端渲染》
關聯倉庫: github.com/zimv/react-…