React Hooks學習之路【1】

想學習的心終於到達了極點,對照着中英文檔、以及一些優質博客對React Hooks進行學習和延伸。
從內置的Hook到自定義Hook。主要是看英文文檔,如果有理解錯誤的地方歡迎指正~

這一篇主要是介紹React Hook是什麼、使用規則、以及useState、useEffect的用法、運行機制、自定義組件的用法。

1. 什麼是React Hooks,它解決了什麼?

A Hook is a special function that lets you “hook into” React features.there names always start with 'use'

React Hooks是16.8的新特性,允許我們不寫class組件也可以使用state和其他React特性,hook本質是一個function

2.使用規則

需滿足可使用插件自動執行這些規則

  • 在function component裏使用
  • 在自定義Hook中使用
  • 不能在循環、條件、嵌套語句裏使用hook。如此是爲了保證hook每次render的調用執行順序不變【如果想,可以在鉤子內部使用】
  • 在function component最頂層調用確保hook在每一次渲染中都按照同樣的順序被調用,這讓react能夠在多次useState、useEffect調用之間保持hook狀態的正確【其實這裏剛開始看並不能理解,需要多看看解釋和代碼】

兩個插件約束此規則

npm install eslint-plugin-react-hooks --save-dev

// Your ESLint configuration
{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
    "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
  }
}

3. 基礎的Hooks用法

useState

是一個可以讓我們在函數組件裏添加React state的鉤子, 什麼時候可以使用Hook呢?如果有一個function component並且它也需要一些自己的state去維護,之前的寫法是抽象出來一個class component,但是現在我們直接可以在function component裏使用useState鉤子。
useState的用法如下方代碼,註釋是在控制檯打出的,可以類比React的state來理解。

const [count, setCount] = useState(0);
//  在控制檯打印出useState(0)的值,是一個數組  [0, ƒ] ,第一個元素是傳入的初始值 可以是基本類型 / 引用類型,
//  第二個是函數  a function that updates it.  
//  []是數組的解構賦值
//  值的更新   setCount(count-1)}

另外在useState hook裏,更新state是replace而不是在class component裏的merge。

Hook可多次調用,它內部的state都是隔離的

那麼 React 怎麼知道哪個 state 對應哪個 useState?答案是 React 靠的是 Hook 調用的順序。因爲我們的示例中,Hook 的調用順序在每次渲染中都是相同的,所以它能夠正常工作

function States() {
  // 聲明多個 state 變量!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
  // ...
}

只要 Hook 的調用順序在多次渲染之間保持一致,React 就能正確地將內部 state 和對應的 Hook 進行關聯。【保持順序至關重要,因爲useState的實現是依靠數組】

通常當函數exits,變量也會被清除,下一次調用又是重新開始,但是聲明的state變量會由React保存,可以看下最下方的demo,setCount時初始值都是上一次終值。

在這裏插入圖片描述

useEffect

類似於class component裏的生命週期鉤子。默認情況下,它在第一次渲染之後和每次更新之後都會執行【類比於componentDidMount、componentDidUpdate】。當然我們可以控制它,後面會說到。React 保證了每次運行 effect 的同時,DOM 都已經更新完畢。
useEffect鉤子接受兩個參數 para1是一個函數,para2是一個數組
para1每次傳入的都是不一樣的,這是故意爲之的,通過這種方式才讓我們在effect裏不會讀到髒數據,每一次render都會有一個effect代替前一個,換句話說就是effect是每次render的產物。可以是(非)具名函數、箭頭函數

the function passed to useEffect is going to be different on every render. This is intentional. In fact, 
this is what lets us read the count value from inside the effect without worrying about it getting stale. 
Every time we re-render, we schedule a different effect, replacing the previous one. In a way, this makes
 the effects behave more like a part of the render result — each effect “belongs” to a particular render.

effects run for every render and not just once. This is why React also cleans up effects from the previous
render before running the effects next time. 

para2有三種情況

  • 不傳。para1每次都會執行;如果有返回一個函數,此函數也是會執行【第一次不會執行,之後每次render會執行】
  • 傳。是空數組,類似於componentDidMount,只會在掛載的時候執行;如果有返回一個函數,那這個函數也只執行一次
  • 傳。數組裏存在元素,數組元素對應的變量只要有一個改變了就會執行鉤子;如果有返回一個函數,那這個函數執行情況與鉤子一樣,推薦啓用 eslint-plugin-react-hooks 中的 exhaustive-deps 規則。此規則會在添加錯誤依賴時發出警告並給出修復建議。

如果要使用第二個參數來規避多次rerender要注意依賴列表的值!必須包含所有組件作用域裏隨時間改變並且在effect用到的值(例如props, state),剛開始這裏並不理解什麼意思,對照文檔後面的QA章節的代碼才豁然開朗,
在這裏插入圖片描述

按上述所說的,useEffect可以返回一個函數,這個函數會在組件卸載的時候執行,它是一個清除機制,類比於componentWillUnmount。不同的是我們的添加、清除邏輯都寫在一起,不像是在react的componentDidMount,componentWillUnmount是分開的且不同的業務處理都在一起

This lets us keep the logic for adding and removing subscriptions close to each other. 

通過上面的解說和代碼可以看出來useEffect的作用,生命週期鉤子經常包含不相關的邏輯,然而又把相關的邏輯分離開來。可以通過寫多個useEffect解決,讓我們的代碼塊看起來更加專注

Hooks let us split the code based on what it is doing rather than a lifecycle method name. 
React will apply every effect used by the component, in the order they were specified.

4.自定義鉤子

作用:有時候我們會想要在組件之間重用一些狀態邏輯。目前爲止,有兩種主流方案來解決這個問題:高階組件和 render props。自定義 Hook 可以讓你在不增加組件的情況下達到同樣的目的。

在第一部分說react Hook本質是一個function,函數名是以‘use’爲開頭,自定義Hook也是如此,如果不這麼做,我們將無法判斷分辨一個函數是否包含調用內置鉤子,就無法自動檢測Hook是否違反規則。在Hook裏也可以調用其他內置/自定義的Hook。

Its name should always start with use so that you can tell at a glance that the rules of Hooks apply to it.
This convention is very important. Without it, we wouldn’t be able to automatically check for violations of rules of 
Hooks because we couldn’t tell if a certain function contains calls to Hooks inside of it.
  • 命名以‘use’開頭
  • 當使用自定義Hook時,內部的所有state和effects都是隔離的
		function useMyHook(count){
            const [can,setCan] = useState(false);
            useEffect(() => {
                setCan(count > 3);
                return () => {
                    console.log('useMyHook - clean - up');
                };
            });
            return can;
        }

		//使用自定義Hook,並且在多個 Hook 之間傳遞信息
		
		const can = useMyHook(count);
        const can1 = useMyHook(count1);

自定義Hook和內置Hook一樣可以在一個組件內多次調用,每一次的state和effect都是隔離的

下面是我自己的demo

	<script crossorigin src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
    <script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
    <style>
        .line{
            border: 5px solid teal;
            width: 100%;
        }
    </style>
    
<body>

    <div id="app"></div>

    <script type="text/babel">
        const { useState,useEffect } = React;
        function useMyHook(count){
            const [can,setCan] = useState(false);
            useEffect(() => {
                setCan(count > 3);
                return () => {
                    console.log('useMyHook - clean - up');
                };
            });
            return can;
        }
        function Lorry({init}) {
            const [count, setCount] = useState(init);
            const [count1, setCount1] = useState(init);
            const can = useMyHook(count);
            const can1 = useMyHook(count1);
            useEffect(() => {
                document.title = `You clicked ${count} times`;
                console.log('object')
                return function(){
                    console.log('clean up');
                }
            });
            return (
                <div>
                    <p>You clicked {count} times</p>
                    <button onClick={() => setCount(count-1)}>
                        count -
                    </button>
                    <button onClick={() => setCount(count => count+1)}>
                        count +
                    </button>
                    <p>{can + '- can'}</p>
                    <p className="line"></p>
                    <p>You clicked {count1} times--1</p>
                    <button onClick={() => setCount1(count1+1)}>
                        count1 +
                    </button>
                    <p>{can1 + '- can1'}</p>
                    <p>{false}</p>
                    <p>{true}</p>
                </div>
            );
        }
            ReactDOM.render(
                <Lorry init={0}/>,
                document.getElementById('app')
            );
    </script>
</body>

參考

  • https://zhuanlan.zhihu.com/p/50274018

最後還有兩個迷惑的地方

  • useState的re-render機制
  • useState是怎麼記住上一次state的
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章