一個很有意思的hook庫:react-hanger

blank

一個很有意思的hook庫:react-hanger

前言

千呼萬喚始出來,React Hooks終於在React 16.8版本中發布穩定版了。最近逛github發現了一個很有意思的庫: react-hanger

複習React Hooks

如果對Hooks還不怎麼了解的同學,建議去看一下官方文檔: Introducing Hooks .

什麼是Hooks?

我們都知道,在Hooks之前,開發react組件主要是class組件和function組件。 function組件沒有state,所以也叫SFC(stateless functional component),簡單的將props映射成view;class組件有state,能夠處理更加複雜的邏輯。但是基於class的組建並不是完美的,總結起來就像Dan說的那樣,有三個主要的問題:

  1. 代碼重用:在hooks出來之前,常見的代碼重用方式是HOCs和render props,這兩種方式帶來的問題是:你需要解構自己的組件,非常的笨重,同時會帶來很深的組件嵌套
  2. 複雜的組件邏輯:在class組件中,有許多的lifecycle 函數,你需要在各個函數的里面去做對應的事情。這種方式帶來的痛點是:邏輯分散在各處,開發者去維護這些代碼會分散自己的精力,理解代碼邏輯也很吃力
  3. class組件的困惑:對於初學者來說,需要理解class組件裡面的this是比較吃力的(這個理由有點勉強~),同時,基於class的組件難以優化(舉個不恰當的例子,看一下babel轉移出來的class代碼量增長了多少)

為了解決上面的這三個問題,react hooks提案登場了,它有以下幾個特點:

  1. 無痛接入,不破壞現有的項目結構
  2. 完全向後兼容,不包含任何不兼容breaking changes
  3. 現在就能使用

Hooks 允許你在不編寫class 的情況下使用狀態(state)和其他React 特性。你還可以構建自己的Hooks ,跨組件共享可重用的有狀態邏輯。

現在React中內置的Hooks有:

當然了,授之以魚不如授之以漁,React官方也提供了教你如何封裝自己Hook的文檔Building Your Own Hooks ,有興趣的小伙伴可以去閱讀一下。

react-hanger初窺

大致的看了下react-hanger的源碼之後發現,這個庫其實是對React Hooks API的適用性封裝。暴露一些更常用的Hooks節省大家造輪子的工作量。

React的核心開發者Dan看到這個庫也做了評價:

一個對Hooks的隱喻。你可以將你的state“掛起”在你的function component上,等你回來的時候,它就掛在那。

blank

本文寫作時,react-hanger的Usage裡提供了6個API,從名字裡就可以看出這些Hook都是做什麼的(Hooks都以"use"開頭,這是一種約定),

import{useInput,useBoolean,useNumber,useArray,useOnMount,useOnUnmount}from"react-hanger";

使用起來也很簡單,比如useNumber

constApp=()=>{constshowCounter=useBoolean(true);constcounter=useNumber(0);return(<div><buttononClick={counter.increase}>increase</button>{showCounter.value&&<span>{counter.value}</span>}<buttononClick={counter.decrease}>decrease</button></div>);};

初步印象:大致與原始的basic hooks有點不同的是,useState返回一個數組,分別是操作,而react-hanger提供的API貌似是將一些操作封裝到一個對像中,比如counter就是一個{value: count, increase: setCount(count + 1), decrease: setCount(count - 1) }的對象。

還有更多的操作方法可以看react-hanger的sandbox: codesandbox.io/s/44m70x

react-hanger源碼淺析

其實翻看了react-hanger的源碼之後會發現,react-hanger一共引用了四個React內置的Hook,

import{useCallback,useEffect,useRef,useState}from"react";

然後返回一些“輪子”hooks,包括useNumberuseArrayuseBoolean等等。

這些輪子可以大致分為兩類:封裝Hook和拆分Hook。

封裝Hook

比如useStatefuluseNumberuseArrayuseBoolean都是對內置Hook useState的封裝。

useStateful

exportconstuseStateful=initial=>{const[value,setValue]=useState(initial);return{value,setValue};};

利用ES6的解構賦值,將useState返回的數組封裝成一個對象重新返回,方便調用。

useNumber

exportconstuseNumber=(initial,{upperLimit,lowerLimit,loop,step=1}={})=>{const[value,setValue]=useState(initial);return{value,setValue,increase:useCallback(i=>{setValue(...);},[]),decrease:useCallback(d=>{setValue(...);},[])};};

useNumber接收一個initial number和一個配置項對象,在內部是通過對initial number進行useState Hook,返回一個對象,除了基本的valuesetValue ,還有兩個方法increasedecrease 。這兩個方法都是用useCallbacksetValue進行的進一步封裝。

useCallback是一個比較重要的內置Hook, useCallback的可以於緩存了每次渲染時inline callback的實例,在第二個參數數組內的值發生更改時才會更改。
這樣可以配合上子組件的shouldComponentUpdate或者useMemo起到減少不必要的渲染的作用。

而第二個參數為空數組的意思就是告訴React不管參數如何都要記憶。

useArray & useBoolean & useInput

至於useArrayuseBooleanuseInput這三個hook可以說和useNumber大同小異,都是需要一個傳入的initial值,在hook內部通過useState初始化,再返回一些常用的操作方法。

這裡的useInput是針對於受控組件,所以不需要useRef

useSetState

exportconstuseSetState=initialValue=>{const{value,setValue}=useStateful(initialValue);return{setState:useCallback(v=>{returnsetValue(oldValue=>({...oldValue,...(typeofv==="function"?v(oldValue):v)}));},[]),state:value};};

Unlike the setState method found in class components, useState does not automatically merge update objects.
與類組件中的setState方法不同,useState不會自動合併更新對象。

熟悉React Hook的同學看了代碼就知道這個hook是封裝了什麼了,因為useState返回的類似於setCount的方法不會自動合併更新對象。這個hook幫助大家可以獲得一個可以merge之前value的Hook型setState

拆分Hook

上述幾個算是封裝hook,那麼下面的幾個就可以算是拆分hook,對useEffect更精細化的處理。

useOnMount & useOnUnmount

眾所周知, useEffect是被用來處理一些原先放在class組件中生命週期函數的副作用,比如componentDidMountcomponentDidUpdatecomponentWillUnmount ,集合而成的一個Hook。

理論上,在每次渲染後都會觸發useEffect的效果,但是如果我只想在didmount里或者只想在willunmount裡做一下事情,該怎麼辦?

這時就用到了useEffect的一個特點:第二個參數為效果依賴的值數組,也就是說只有當數組內的值變化才會觸發useEffect

useEffect(()=>{constsubscription=props.source.subscribe();return()=>{subscription.unsubscribe();};},[props.source],);

而如果第二個參數為一個空數組的時候,則相當於告訴React你的效果不依賴於組件中的任何值,因此該效果只能在mount上運行並在unmount上清理,它不會在更新時運行

exportconstuseOnUnmount=onUnmount=>useEffect(()=>{return()=>onUnmount&&onUnmount();},[]);exportconstuseOnMount=onMount=>useEffect(()=>{onMount&&onMount();},[]);

所以useOnMount的實現就非常簡單,在useEffect內執行onMount函數且第二個參數是[]useOnUnmount的實現則是返回onUnmount函數且第二個參數是[]

useLifecycleHooks

exportconstuseLifecycleHooks=({onMount,onUnmount})=>useEffect(()=>{onMount&&onMount();return()=>onUnmount&&onUnmount();},[]);

useLifecycleHooks則是對useOnUnmountuseOnMount的整合,在useEffect的第二個參數為[]的情況下,執行onMount和返回onUnmount。

useLogger

exportconstuseLogger=(name,props)=>{useLifecycleHooks({onMount:()=>console.log(`${name} has mounted`),onUnmount:()=>console.log(`${name} has unmounted`)});useEffect(()=>{console.log("Props updated",props);});};

useLogger算是一個為hook commponent封裝的log插件,通過在useLifecycleHooks內傳入onMount和onUnmount打印日誌的函數,之後再通過原生的默認useEffect不傳遞第二個參數來實現在更新過程中打印日誌。

usePrevious

exportconstusePrevious=value=>{constref=useRef();useEffect(()=>{ref.current=value;});returnref.current;};

usePrevious則可以獲取之前的props或者state,來自於React的官方文檔

總結

其實react-hanger的源代碼都很簡潔,對源碼感興趣的同學請看: github.com/kitze/react- ,更多的是這個庫的實現大部分都是用了React Hook的思想,希望大家可以通過本文加深對React Hook的理解和認識。

What do you think?

Written by marketer

blank

CSS愛好者們,第5屆CSS大會來啦!

blank

2019 力扣杯—全國高校春季編程大賽等你來戰!