做者最初使用React Hooks
的場景就是用Hooks
重構現有的 Class 組件。javascript
改寫 Class 組件也沒有很難, 對照官網 API, 直接暴力輸出java
useState
替代this.state
useCallback
替代 Class 的方法useEffect
替代生命週期函數首先, 來看一個使用 Class 實現的組件, 支持列表的增刪查改(很差意思, 只實現了增);react
按照上面的暴力輸出思路和官網提供的Hooks
API, 咱們將一個 Class 組件改形成了 Function + Hooks 組件試試。數組
看上去好像沒什麼問題, 有一種重構代碼大功告成
的感受。可心裏總有一點點不安, 這不安來自於哪裏呢?架構
在身邊的同事看到這段代碼後, 吐槽地說了一句, 使用React Hooks看上去好亂阿, 在一個函數裏寫了一坨代碼, 不像OOP那樣邏輯清晰
。框架
仔細想一想, 好像確實是這個樣子的。 重構後的組件代碼邏輯都在同一個函數中, 看上去邏輯不清晰、可閱讀性不好、維護困難。ide
這麼簡單的組件改寫後都這麼亂了, 邏輯更復雜一些的組件, 就是更大一坨的意大利麪了==函數式編程
放棄使用 React Hooks? 難道它就真的不香嗎?!
考慮到本身的代碼能力是渣中本渣, 因此必定是本身的使用姿式有問題了。那就看看能不能繼續優化下去吧!
在上面代碼中, 使用useEffect
模擬了componentDidMount
這一輩子命週期。React 官方推薦使用多個useEffect
去完成不一樣的事情, 而不是放在一塊兒。那咱們能夠對此進行必定的封裝處理。
// useMount sourcecode
import { useEffect } from 'react';
const noop = () => {};
export function useMount(mount, unmount = noop) {
useEffect(() => {
mount();
return () => {
unmount();
};
}, []);
}
複製代碼
// use case
export default function Demo(props) {
useMount(() => {
// do something
// after didMount
});
}
複製代碼
在上述組件中, 使用了useState
, 並在添加
,編輯
,刪除
等操做中都調用了修改 State 的setXXX
方法。
像這樣的數據-操做
有着相關聯繫的, 咱們能夠封裝本身的 Hooks
import { useState, useCallback } from 'react';
export function useList(initial = []) {
const [list, setList] = useState(initial);
const add = useCallback(data => {
// setXXX使用函數後, 入參會拿到最新的state數據
setList(list => [...list, data]);
}, []);
const edit = useCallback(data => {}, [list]);
const deleteOne = useCallback(data => {}, [list]);
return [list, add, edit, deleteOne];
}
複製代碼
上述數據結構-操做方法
只是最簡單的一種方案。更多的使用方法和抽象封裝要具體狀況具體分析來使用了。
因此在上述封裝以後,
export default function Demo() {
const [list, add, edit, deleteOne] = useList([]);
const handleAdd = useCallback(() => {
add({
title: `local data ${list.length}`,
description: 'description test'
});
}, []);
const handleEdit = useCallback(() => {}, []);
const handleDelete = useCallback(() => {}, []);
usetMount(() => {});
//...
// 其餘不變
}
複製代碼
方法論主要解決「怎麼辦」的問題。
下文只是我的對React Hooks
的實踐總結, 若是有更好的思路歡迎來拍磚。
這裏提到的方法論, 是在使用函數組件 + React Hooks時, 咱們該怎麼合理的應用它。
首先, 將注意力回到最初提到的函數組件
, 它實際上就是數據=>視圖
, 一組特殊的輸入輸出的函數。
v = f(p)
複製代碼
函數組件與React Hooks體現的都是函數式編程的思想, 即:
函數式編程是一種編程範式,它將電腦運算視爲函數運算,而且避免使用程序狀態以及易變對象。其中,λ 演算(lambda calculus)爲該語言最重要的基礎。並且,λ 演算的函數能夠接受函數看成輸入(引數)和輸出(傳出值)。 比起指令式編程,函數式編程更增強調程序執行的結果而非執行的過程,倡導利用若干簡單的執行單元讓計算結果不斷漸進,逐層推導複雜的運算,而不是設計一個複雜的執行過程。
在上文中提到了數據 -> 視圖 -> SideEffect
這樣的流程。
每一次渲染都是不一樣狀況引發 -> 數據變動 -> 視圖更新 -> 執行SideEffect
的過程, 這是一條主線的渲染流程, 那能夠進行以下重組:
每一次渲染
=== 多個(數據, 操做指令, 反作用)
=> 視圖
=> SideEffect處理
咱們更須要關心(數據, 操做指令, 反作用)
這個元組, 如何將多個元組在這個渲染流程中合併成一條數據 -> 視圖 -> SideEffect
是React Fiber架構實現的事情。這個心智操做由React框架解決。咱們只要正確實現(數據, 操做指令, 反作用)
的封裝就行了。
而這是咱們可使用React Hooks
作到的事情, 也是咱們如何合理的封裝使用React Hooks
的方法論。
我的以爲倒計時
是一個不錯的例子
import React, { useState, useCallback, useEffect, useRef } from "react";
export function useCount() {
const [count, setCount] = useState(0);
const timer = useRef(null);
useEffect(() => {
timer.current = setTimeout(() => {
if (count > 0) {
setCount(count - 1);
}
}, 1000);
return () => {
clearTimeout(timer.current);
};
}, [count]);
const startCount = useCallback(count => {
setCount(count);
}, []);
const stopCount = useCallback(() => {
clearTimeout(timer.current);
}, []);
return [count, startCount, stopCount];
}
複製代碼
咱們使用useCount
這個封裝的Hook, 提供了數據->count
, 操做->startCount
, 內部使用useEffect
封裝了使用setTimeout
倒計時的邏輯。
本文着重點在於如何合理地使用React Hooks, 提出了對書寫函數組件+ReactHooks
的方法論的思考。
封裝Hooks也是基於可擴展可維護的實用角度出發。 本文也是提醒本身不要爲了寫Hooks而寫Hooks。