React Hooks 從入門到放棄(二)

Hooks 的合理使用

做者最初使用React Hooks的場景就是用Hooks重構現有的 Class 組件。javascript

改寫 Class 組件也沒有很難, 對照官網 API, 直接暴力輸出java

  1. 使用useState替代this.state
  2. 使用useCallback替代 Class 的方法
  3. 使用useEffect替代生命週期函數

首先, 來看一個使用 Class 實現的組件, 支持列表的增刪查改(很差意思, 只實現了增);react

Edit On CodeSandbox 編程

重構組件

按照上面的暴力輸出思路和官網提供的HooksAPI, 咱們將一個 Class 組件改形成了 Function + Hooks 組件試試。數組

Edit on CodeSandbox數據結構

看上去好像沒什麼問題, 有一種重構代碼大功告成的感受。可心裏總有一點點不安, 這不安來自於哪裏呢?架構

在身邊的同事看到這段代碼後, 吐槽地說了一句, 使用React Hooks看上去好亂阿, 在一個函數裏寫了一坨代碼, 不像OOP那樣邏輯清晰框架

仔細想一想, 好像確實是這個樣子的。 重構後的組件代碼邏輯都在同一個函數中, 看上去邏輯不清晰、可閱讀性不好、維護困難。ide

這麼簡單的組件改寫後都這麼亂了, 邏輯更復雜一些的組件, 就是更大一坨的意大利麪了==函數式編程

代碼優化

放棄使用 React Hooks? 難道它就真的不香嗎?!

考慮到本身的代碼能力是渣中本渣, 因此必定是本身的使用姿式有問題了。那就看看能不能繼續優化下去吧!

1. 封裝語義化 useEffect

在上面代碼中, 使用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
    });
}

複製代碼

2. 封裝 Hooks

在上述組件中, 使用了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倒計時的邏輯。

Edit on CodeSandbox

總結

本文着重點在於如何合理地使用React Hooks, 提出了對書寫函數組件+ReactHooks的方法論的思考。

封裝Hooks也是基於可擴展可維護的實用角度出發。 本文也是提醒本身不要爲了寫Hooks而寫Hooks。

相關文章
相關標籤/搜索