国产亚洲精品福利在线无卡一,国产精久久一区二区三区,亚洲精品无码国模,精品久久久久久无码专区不卡

當(dāng)前位置: 首頁 > news >正文

黃驊市第三中學(xué)關(guān)鍵詞優(yōu)化包年推廣

黃驊市第三中學(xué),關(guān)鍵詞優(yōu)化包年推廣,wordpress閱讀器,成都網(wǎng)站建設(shè)科文章目錄 用 State 響應(yīng)輸入聲明式地考慮 UI步驟 1:定位組件中不同的視圖狀態(tài)步驟 2:確定是什么觸發(fā)了這些狀態(tài)的改變步驟 3:通過 useState 表示內(nèi)存中的 state步驟 4:刪除任何不必要的 state 變量步驟 5:連接事件處理…

文章目錄

  • 用 State 響應(yīng)輸入
    • 聲明式地考慮 UI
      • 步驟 1:定位組件中不同的視圖狀態(tài)
      • 步驟 2:確定是什么觸發(fā)了這些狀態(tài)的改變
      • 步驟 3:通過 useState 表示內(nèi)存中的 state
      • 步驟 4:刪除任何不必要的 state 變量
      • 步驟 5:連接事件處理函數(shù)以設(shè)置 state
  • 選擇 State 結(jié)構(gòu)
    • 構(gòu)建 state 的原則
  • 在組件間共享狀態(tài)
    • 舉例說明一下狀態(tài)提升
      • 第 1 步: 從子組件中移除狀態(tài)
      • 第 2 步: 從公共父組件傳遞硬編碼數(shù)據(jù)
      • 第 3 步: 為公共父組件添加狀態(tài)
        • 受控組件和非受控組件
  • 對(duì) state 進(jìn)行保留和重置
    • 狀態(tài)與渲染樹中的位置相關(guān)
    • 相同位置的相同組件會(huì)使得 state 被保留下來
    • 相同位置的不同組件會(huì)使 state 重置
    • 在相同位置重置 state
      • 方法一:將組件渲染在不同的位置
      • 方法二:使用 key 來重置 state
        • 為被移除的組件保留 state
  • 遷移狀態(tài)邏輯至 Reducer 中
    • 使用 reducer 整合狀態(tài)邏輯
      • 第 1 步: 將設(shè)置狀態(tài)的邏輯修改成 dispatch 的一個(gè) action
      • 第 2 步: 編寫一個(gè) reducer 函數(shù)
      • 語法
      • 示例
      • 使用場景
      • 注意事項(xiàng)
      • 示例
      • 第 3 步: 在組件中使用 reducer
    • 對(duì)比 useState 和 useReducer
  • 使用 Context 深層傳遞參數(shù)
    • 傳遞 props 帶來的問題
    • Context:傳遞 props 的另一種方法
    • 寫在你使用 context 之前
    • Context 的使用場景
  • 使用 Reducer 和 Context 拓展你的應(yīng)用
  • 應(yīng)急方案
  • 使用 ref 引用值
    • 給你的組件添加 ref
    • ref 和 state 的不同之處
        • useRef 內(nèi)部是如何運(yùn)行的?
    • 何時(shí)使用 ref
    • ref 的最佳實(shí)踐
  • 使用 ref 操作 DOM
    • 獲取指向節(jié)點(diǎn)的 ref
    • 訪問另一個(gè)組件的 DOM 節(jié)點(diǎn)
  • 使用 Effect 同步
    • 如何編寫 Effect
      • 第一步:聲明 Effect
      • 第二步:指定 Effect 依賴
      • 第三部:按需添加清理(cleanup)函數(shù)
      • 訂閱事件
      • 觸發(fā)動(dòng)畫
      • 初始化應(yīng)用時(shí)不需要使用 Effect 的情形
  • 你可能不需要 Effect
  • 響應(yīng)式 Effect 的生命周期
    • Effect 的生命周期

[]以下內(nèi)容來自官方文檔 https://zh-hans.react.dev/learn](https://zh-hans.react.dev/learn)

用 State 響應(yīng)輸入

React 控制 UI 的方式是聲明式的。你不必直接控制 UI 的各個(gè)部分,只需要聲明組件可以處于的不同狀態(tài),并根據(jù)用戶的輸入在它們之間切換

聲明式地考慮 UI

你已經(jīng)從上面的例子看到如何去實(shí)現(xiàn)一個(gè)表單了,為了更好地理解如何在 React 中思考,接下來你將會(huì)學(xué)到如何用 React 重新實(shí)現(xiàn)這個(gè) UI:

  1. 定位你的組件中不同的視圖狀態(tài)
  2. 確定是什么觸發(fā)了這些 state 的改變
  3. 表示內(nèi)存中的 state(需要使用 useState)
  4. 刪除任何不必要的 state 變量
  5. 連接事件處理函數(shù)去設(shè)置 state

步驟 1:定位組件中不同的視圖狀態(tài)

在計(jì)算機(jī)科學(xué)中,你或許聽過可處于多種“狀態(tài)”之一的 “狀態(tài)機(jī)”
首先,你需要去可視化 UI 界面中用戶可能看到的所有不同的“狀態(tài)”:

  • 無數(shù)據(jù):表單有一個(gè)不可用狀態(tài)的“提交”按鈕。
  • 輸入中:表單有一個(gè)可用狀態(tài)的“提交”按鈕。
  • 提交中:表單完全處于不可用狀態(tài),加載動(dòng)畫出現(xiàn)。
  • 成功時(shí):顯示“成功”的消息而非表單。
  • 錯(cuò)誤時(shí):與輸入狀態(tài)類似,但會(huì)多錯(cuò)誤的消息。

步驟 2:確定是什么觸發(fā)了這些狀態(tài)的改變

你可以觸發(fā) state 的更新來響應(yīng)兩種輸入:

  • 人為輸入。比如點(diǎn)擊按鈕、在表單中輸入內(nèi)容,或?qū)Ш降芥溄印?/li>
  • 計(jì)算機(jī)輸入。比如網(wǎng)絡(luò)請(qǐng)求得到反饋、定時(shí)器被觸發(fā),或加載一張圖片

你需要改變 state 以響應(yīng)幾個(gè)不同的輸入:

  • 改變輸入框中的文本時(shí)(人為)應(yīng)該根據(jù)輸入框的內(nèi)容是否是空值,從而決定將表單的狀態(tài)從空值狀態(tài)切換到輸入中或切換回原狀態(tài)。
  • 點(diǎn)擊提交按鈕時(shí)(人為)應(yīng)該將表單的狀態(tài)切換到提交中的狀態(tài)。
  • 網(wǎng)絡(luò)請(qǐng)求成功后(計(jì)算機(jī))應(yīng)該將表單的狀態(tài)切換到成功的狀態(tài)。
  • 網(wǎng)絡(luò)請(qǐng)求失敗后(計(jì)算機(jī))應(yīng)該將表單的狀態(tài)切換到失敗的狀態(tài),與此同時(shí),顯示錯(cuò)誤信息
  • image.png

步驟 3:通過 useState 表示內(nèi)存中的 state

如果你很難立即想出最好的辦法,那就先從添加足夠多的 state 開始,確保所有可能的視圖狀態(tài)都囊括其中:

const [isEmpty, setIsEmpty] = useState(true);const [isTyping, setIsTyping] = useState(false);const [isSubmitting, setIsSubmitting] = useState(false);const [isSuccess, setIsSuccess] = useState(false);const [isError, setIsError] = useState(false);

你最初的想法或許不是最好的,但是沒關(guān)系,重構(gòu) state 也是步驟中的一部分!

步驟 4:刪除任何不必要的 state 變量

在清理之后,你只剩下 3 個(gè)(從原本的 7 個(gè)!)_必要_的 state 變量:

const [answer, setAnswer] = useState('');const [error, setError] = useState(null);const [status, setStatus] = useState('typing'); // 'typing', 'submitting', or 'success'

步驟 5:連接事件處理函數(shù)以設(shè)置 state

練習(xí)1

import {useState} from 'react'
export default function Picture() {const [imgcss,setImgcss] = useState(false)function handleBgd(){setImgcss(false)}function handleImg(e){e.stopPropagation()setImgcss(true)}let bgdClassName = 'background'let imgClassName = 'picture'if(imgcss){imgClassName += ' picture--active'  //注意此處的空格}else{bgdClassName += ' background--active'  //注意此處的空格}return (<div className={bgdClassName} onClick={()=>setImgcss(false)}><imgonClick={e => {e.stopPropagation();setImgcss(true)}}className={imgClassName}alt="Rainbow houses in Kampung Pelangi, Indonesia"src="https://i.imgur.com/5qwVYb1.jpeg"/></div>);
}

練習(xí)2

import {useState} from 'react'
export default function EditProfile() {const [edit,setEdit]= useState(false)const [firstName,setFirstName] = useState('')const [lastName,setLastName] = useState('')return (<form onSubmit={(e)=>{e.preventDefault()setEdit(!edit)}}><label>First name:{' '}{edit? <input onChange={(e)=>{setFirstName(e.target.value)}} value={firstName}/>:<b>{firstName}</b>}</label><label>Last name:{' '}{edit? <input onChange={(e)=>{setLastName(e.target.value)}} value={lastName}/>:<b>{lastName}</b>}</label><button type="submit">{edit? 'save ': 'edit '} Profile</button><p><i>Hello, {firstName} {lastName}!</i></p></form>);
}

選擇 State 結(jié)構(gòu)

構(gòu)建 state 的原則

當(dāng)你編寫一個(gè)存有 state 的組件時(shí),你需要選擇使用多少個(gè) state 變量以及它們都是怎樣的數(shù)據(jù)格式。盡管選擇次優(yōu)的 state 結(jié)構(gòu)下也可以編寫正確的程序,但有幾個(gè)原則可以指導(dǎo)您做出更好的決策:

  1. 合并關(guān)聯(lián)的 state。如果你總是同時(shí)更新兩個(gè)或更多的 state 變量,請(qǐng)考慮將它們合并為一個(gè)單獨(dú)的 state 變量。
  2. 避免互相矛盾的 state。當(dāng) state 結(jié)構(gòu)中存在多個(gè)相互矛盾或“不一致”的 state 時(shí),你就可能為此會(huì)留下隱患。應(yīng)盡量避免這種情況。
  3. 避免冗余的 state。如果你能在渲染期間從組件的 props 或其現(xiàn)有的 state 變量中計(jì)算出一些信息,則不應(yīng)將這些信息放入該組件的 state 中。
  4. 避免重復(fù)的 state。當(dāng)同一數(shù)據(jù)在多個(gè) state 變量之間或在多個(gè)嵌套對(duì)象中重復(fù)時(shí),這會(huì)很難保持它們同步。應(yīng)盡可能減少重復(fù)。
  5. 避免深度嵌套的 state。深度分層的 state 更新起來不是很方便。如果可能的話,最好以扁平化方式構(gòu)建 state。

在組件間共享狀態(tài)

有時(shí)候,你希望兩個(gè)組件的狀態(tài)始終同步更改。要實(shí)現(xiàn)這一點(diǎn),可以將相關(guān) state 從這兩個(gè)組件上移除,并把 state 放到它們的公共父級(jí),再通過 props 將 state 傳遞給這兩個(gè)組件。這被稱為“狀態(tài)提升”,這是編寫 React 代碼時(shí)常做的事

舉例說明一下狀態(tài)提升

在這個(gè)例子中,父組件 Accordion 渲染了 2 個(gè)獨(dú)立的 Panel 組件。

  • Accordion
    • Panel
    • Panel

每個(gè) Panel 組件都有一個(gè)布爾值 isActive,用于控制其內(nèi)容是否可見

import { useState } from 'react';function Panel({ title, children }) {const [isActive, setIsActive] = useState(false);return (<section className="panel"><h3>{title}</h3>{isActive ? (<p>{children}</p>) : (<button onClick={() => setIsActive(true)}>顯示</button>)}</section>);
}export default function Accordion() {return (<><h2>哈薩克斯坦,阿拉木圖</h2><Panel title="關(guān)于">阿拉木圖人口約200萬,是哈薩克斯坦最大的城市。它在 1929 年到 1997 年間都是首都。</Panel><Panel title="詞源">這個(gè)名字來自于 <span lang="kk-KZ">алма</span>,哈薩克語中“蘋果”的意思,經(jīng)常被翻譯成“蘋果之鄉(xiāng)”。事實(shí)上,阿拉木圖的周邊地區(qū)被認(rèn)為是蘋果的發(fā)源地,<i lang="la">Malus sieversii</i> 被認(rèn)為是現(xiàn)今蘋果的祖先。</Panel></>);
}

image.png假設(shè)現(xiàn)在您想改變這種行為,以便在任何時(shí)候只展開一個(gè)面板。在這種設(shè)計(jì)下,展開第 2 個(gè)面板應(yīng)會(huì)折疊第 1 個(gè)面板。您該如何做到這一點(diǎn)呢?”
要協(xié)調(diào)好這兩個(gè)面板,我們需要分 3 步將狀態(tài)“提升”到他們的父組件中。

  1. 從子組件中 移除 state 。
  2. 從父組件 傳遞 硬編碼數(shù)據(jù)。
  3. 為共同的父組件添加 state ,并將其與事件處理函數(shù)一起向下傳遞

第 1 步: 從子組件中移除狀態(tài)

你將把 Panel 組件對(duì) isActive 的控制權(quán)交給他們的父組件。這意味著,父組件會(huì)將 isActive 作為 prop 傳給子組件 Panel。我們先從 Panel 組件中 刪除下面這一行

const [isActive, setIsActive] = useState(false);

然后,把 isActive 加入 Panel 組件的 props 中:

function Panel({ title, children, isActive }) {

第 2 步: 從公共父組件傳遞硬編碼數(shù)據(jù)

import { useState } from 'react';export default function Accordion() {return (<><h2>哈薩克斯坦,阿拉木圖</h2><Panel title="關(guān)于" isActive={true}>阿拉木圖人口約200萬,是哈薩克斯坦最大的城市。它在 1929 年到 1997 年間都是首都。</Panel><Panel title="詞源" isActive={true}>這個(gè)名字來自于 <span lang="kk-KZ">алма</span>,哈薩克語中“蘋果”的意思,經(jīng)常被翻譯成“蘋果之鄉(xiāng)”。事實(shí)上,阿拉木圖的周邊地區(qū)被認(rèn)為是蘋果的發(fā)源地,<i lang="la">Malus sieversii</i> 被認(rèn)為是現(xiàn)今蘋果的祖先。</Panel></>);
}function Panel({ title, children, isActive }) {return (<section className="panel"><h3>{title}</h3>{isActive ? (<p>{children}</p>) : (<button onClick={() => setIsActive(true)}>顯示</button>)}</section>);
}

第 3 步: 為公共父組件添加狀態(tài)

狀態(tài)提升通常會(huì)改變原狀態(tài)的數(shù)據(jù)存儲(chǔ)類型。
在這個(gè)例子中,一次只能激活一個(gè)面板。這意味著 Accordion 這個(gè)父組件需要記錄 哪個(gè) 面板是被激活的面板。我們可以用數(shù)字作為當(dāng)前被激活 Panel 的索引,而不是 boolean 值:

const [activeIndex, setActiveIndex] = useState(0);
import { useState } from 'react';export default function Accordion() {const [activeIndex, setActiveIndex] = useState(0);return (<><h2>哈薩克斯坦,阿拉木圖</h2><Paneltitle="關(guān)于"isActive={activeIndex === 0}onShow={() => setActiveIndex(0)}>阿拉木圖人口約200萬,是哈薩克斯坦最大的城市。它在 1929 年到 1997 年間都是首都。</Panel><Paneltitle="詞源"isActive={activeIndex === 1}onShow={() => setActiveIndex(1)}>這個(gè)名字來自于 <span lang="kk-KZ">алма</span>,哈薩克語中“蘋果”的意思,經(jīng)常被翻譯成“蘋果之鄉(xiāng)”。事實(shí)上,阿拉木圖的周邊地區(qū)被認(rèn)為是蘋果的發(fā)源地,<i lang="la">Malus sieversii</i> 被認(rèn)為是現(xiàn)今蘋果的祖先。</Panel></>);
}function Panel({title,children,isActive,onShow
}) {return (<section className="panel"><h3>{title}</h3>{isActive ? (<p>{children}</p>) : (<button onClick={onShow}>顯示</button>)}</section>);
}

image.png

受控組件和非受控組件


通常我們把包含“不受控制”狀態(tài)的組件稱為“非受控組件”。例如,最開始帶有 isActive 狀態(tài)變量的 Panel 組件就是不受控制的,因?yàn)槠涓附M件無法控制面板的激活狀態(tài)。
相反,當(dāng)組件中的重要信息是由 props 而不是其自身狀態(tài)驅(qū)動(dòng)時(shí),就可以認(rèn)為該組件是“受控組件”。這就允許父組件完全指定其行為。最后帶有 isActive 屬性的 Panel 組件是由 Accordion 組件控制的
練習(xí)1

import { useState } from 'react';export default function SyncedInputs() {const [text,setText]= useState('')function handleChange(e){setText(e.target.value)}return (<><Input label="第一個(gè)輸入框" onChange={handleChange} text={text}/><Input label="第二個(gè)輸入框" onChange={handleChange} text={text}/></>);
}function Input({ label, onChange, text }) {//const [text, setText] = useState('');/*function handleChange(e) {setText(e.target.value);}*/return (<label>{label}{' '}<inputvalue={text}onChange={onChange}/></label>);
}

練習(xí)2

import { useState } from 'react';
import { foods, filterItems } from './data.js';export default function FilterableList() {const [query,setQuery] = useState('')function handleQuery(e){setQuery(e.target.value)}return (<><SearchBar text={query} onChange={handleQuery}/><hr /><List items={foods} text={query}/></>);
}function SearchBar({text,onChange}) {/*const [query, setQuery] = useState('');function handleChange(e) {setQuery(e.target.value);}*/return (<label>搜索:{' '}<inputvalue={text}onChange={onChange}/></label>);
}function List({ items,text }) {return (<table><tbody>{filterItems(items,text).map(food => (<tr key={food.id}><td>{food.name}</td><td>{food.description}</td></tr>))}</tbody></table>);
}

對(duì) state 進(jìn)行保留和重置

狀態(tài)與渲染樹中的位置相關(guān)

只有當(dāng)在樹中相同的位置渲染相同的組件時(shí),React 才會(huì)一直保留著組件的 state

import { useState } from 'react';export default function App() {const [showB, setShowB] = useState(true);return (<div><Counter />{showB && <Counter />} <label><inputtype="checkbox"checked={showB}onChange={e => {setShowB(e.target.checked)}}/>渲染第二個(gè)計(jì)數(shù)器</label></div>);
}function Counter() {const [score, setScore] = useState(0);const [hover, setHover] = useState(false);let className = 'counter';if (hover) {className += ' hover';}return (<divclassName={className}onPointerEnter={() => setHover(true)}onPointerLeave={() => setHover(false)}><h1>{score}</h1><button onClick={() => setScore(score + 1)}>加一</button></div>);
}

注意,當(dāng)你停止渲染第二個(gè)計(jì)數(shù)器的那一刻,它的 state 完全消失了。這是因?yàn)?React 在移除一個(gè)組件時(shí),也會(huì)銷毀它的 stateimage.png
當(dāng)你重新勾選“渲染第二個(gè)計(jì)數(shù)器”復(fù)選框時(shí),另一個(gè)計(jì)數(shù)器及其 state 將從頭開始初始化(score = 0)并被添加到 DOM 中。
image.png

相同位置的相同組件會(huì)使得 state 被保留下來

import { useState } from 'react';export default function App() {const [isFancy, setIsFancy] = useState(false);return (<div>{isFancy ? (<Counter isFancy={true} /> ) : (<Counter isFancy={false} /> )}<label><inputtype="checkbox"checked={isFancy}onChange={e => {setIsFancy(e.target.checked)}}/>使用好看的樣式</label></div>);
}function Counter({ isFancy }) {const [score, setScore] = useState(0);const [hover, setHover] = useState(false);let className = 'counter';if (hover) {className += ' hover';}if (isFancy) {className += ' fancy';}return (<divclassName={className}onPointerEnter={() => setHover(true)}onPointerLeave={() => setHover(false)}><h1>{score}</h1><button onClick={() => setScore(score + 1)}>加一</button></div>);
}

image.png

相同位置的不同組件會(huì)使 state 重置

在這個(gè)例子中,勾選復(fù)選框會(huì)將 替換為一個(gè)

import { useState } from 'react';export default function App() {const [isPaused, setIsPaused] = useState(false);return (<div>{isPaused ? (<p>待會(huì)見!</p> ) : (<Counter /> )}<label><inputtype="checkbox"checked={isPaused}onChange={e => {setIsPaused(e.target.checked)}}/>休息一下</label></div>);
}function Counter() {const [score, setScore] = useState(0);const [hover, setHover] = useState(false);let className = 'counter';if (hover) {className += ' hover';}return (<divclassName={className}onPointerEnter={() => setHover(true)}onPointerLeave={() => setHover(false)}><h1>{score}</h1><button onClick={() => setScore(score + 1)}>加一</button></div>);
}

image.pngimage.png并且,當(dāng)你在相同位置渲染不同的組件時(shí),組件的整個(gè)子樹都會(huì)被重置。要驗(yàn)證這一點(diǎn),可以增加計(jì)數(shù)器的值然后勾選復(fù)選框

import { useState } from 'react';export default function App() {const [isFancy, setIsFancy] = useState(false);return (<div>{isFancy ? (<div><Counter isFancy={true} /> </div>) : (<section><Counter isFancy={false} /></section>)}<label><inputtype="checkbox"checked={isFancy}onChange={e => {setIsFancy(e.target.checked)}}/>使用好看的樣式</label></div>);
}function Counter({ isFancy }) {const [score, setScore] = useState(0);const [hover, setHover] = useState(false);let className = 'counter';if (hover) {className += ' hover';}if (isFancy) {className += ' fancy';}return (<divclassName={className}onPointerEnter={() => setHover(true)}onPointerLeave={() => setHover(false)}><h1>{score}</h1><button onClick={() => setScore(score + 1)}>加一</button></div>);
}

image.pngimage.png

在相同位置重置 state

方法一:將組件渲染在不同的位置

import { useState } from 'react';export default function Scoreboard() {const [isPlayerA, setIsPlayerA] = useState(true);return (<div>{isPlayerA &&<Counter person="Taylor" />}{!isPlayerA &&<Counter person="Sarah" />}<button onClick={() => {setIsPlayerA(!isPlayerA);}}>下一位玩家!</button></div>);
}function Counter({ person }) {const [score, setScore] = useState(0);const [hover, setHover] = useState(false);let className = 'counter';if (hover) {className += ' hover';}return (<divclassName={className}onPointerEnter={() => setHover(true)}onPointerLeave={() => setHover(false)}><h1>{person} 的分?jǐn)?shù):{score}</h1><button onClick={() => setScore(score + 1)}>加一</button></div>);
}

image.png

方法二:使用 key 來重置 state

key 不只可以用于列表!你可以使用 key 來讓 React 區(qū)分任何組件。默認(rèn)情況下,React 使用父組件內(nèi)部的順序(“第一個(gè)計(jì)數(shù)器”、“第二個(gè)計(jì)數(shù)器”)來區(qū)分組件。但是 key 可以讓你告訴 React 這不僅僅是 第一個(gè) 或者 第二個(gè) 計(jì)數(shù)器,而且還是一個(gè)特定的計(jì)數(shù)器——例如,Taylor 的 計(jì)數(shù)器。這樣無論它出現(xiàn)在樹的任何位置, React 都會(huì)知道它是 Taylor 的 計(jì)數(shù)器!

import { useState } from 'react';export default function Scoreboard() {const [isPlayerA, setIsPlayerA] = useState(true);return (<div>{isPlayerA ? (<Counter key="Taylor" person="Taylor" />) : (<Counter key="Sarah" person="Sarah" />)}<button onClick={() => {setIsPlayerA(!isPlayerA);}}>下一位玩家!</button></div>);
}function Counter({ person }) {const [score, setScore] = useState(0);const [hover, setHover] = useState(false);let className = 'counter';if (hover) {className += ' hover';}return (<divclassName={className}onPointerEnter={() => setHover(true)}onPointerLeave={() => setHover(false)}><h1>{person} 的分?jǐn)?shù):{score}</h1><button onClick={() => setScore(score + 1)}>加一</button></div>);
}

為被移除的組件保留 state

在真正的聊天應(yīng)用中,你可能會(huì)想在用戶再次選擇前一個(gè)收件人時(shí)恢復(fù)輸入 state。對(duì)于一個(gè)不可見的組件,有幾種方法可以讓它的 state “活下去”:

  • 與其只渲染現(xiàn)在這一個(gè)聊天,你可以把 所有 聊天都渲染出來,但用 CSS 把其他聊天隱藏起來。這些聊天就不會(huì)從樹中被移除了,所以它們的內(nèi)部 state 會(huì)被保留下來。這種解決方法對(duì)于簡單 UI 非常有效。但如果要隱藏的樹形結(jié)構(gòu)很大且包含了大量的 DOM 節(jié)點(diǎn),那么性能就會(huì)變得很差。
  • 你可以進(jìn)行 狀態(tài)提升 并在父組件中保存每個(gè)收件人的草稿消息。這樣即使子組件被移除了也無所謂,因?yàn)楸A糁匾畔⒌氖歉附M件。這是最常見的解決方法。
  • 除了 React 的 state,你也可以使用其他數(shù)據(jù)源。例如,也許你希望即使用戶不小心關(guān)閉頁面也可以保存一份信息草稿。要實(shí)現(xiàn)這一點(diǎn),你可以讓 Chat 組件通過讀取 localStorage 對(duì)其 state 進(jìn)行初始化,并把草稿保存在那里。

練習(xí)1

import { useState } from 'react';export default function App() {const [showHint, setShowHint] = useState(false);if (showHint) {return (<div><p><i>提示:你最喜歡的城市?</i></p><Form /><button onClick={() => {setShowHint(false);}}>隱藏提示</button></div>);}return (<div><p></p><Form /><button onClick={() => {setShowHint(true);}}>顯示提示</button></div>);
}function Form() {const [text, setText] = useState('');return (<textareavalue={text}onChange={e => setText(e.target.value)}/>);
}

練習(xí)2

import { useState } from 'react';export default function App() {const [reverse, setReverse] = useState(false);let checkbox = (<label><inputtype="checkbox"checked={reverse}onChange={e => setReverse(e.target.checked)}/>調(diào)換順序</label>);if (reverse) {return (<><Field label="姓氏" key='姓氏' /> <Field label="名字" key='名字'/>{checkbox}</>);} else {return (<><Field label="名字" key='名字'/> <Field label="姓氏" key='姓氏'/>{checkbox}</>);    }
}function Field({ label }) {const [text, setText] = useState('');return (<label>{label}<inputtype="text"value={text}placeholder={label}onChange={e => setText(e.target.value)}/></label>);
}

練習(xí)3,4,5

遷移狀態(tài)邏輯至 Reducer 中

使用 reducer 整合狀態(tài)邏輯

隨著組件復(fù)雜度的增加,你將很難一眼看清所有的組件狀態(tài)更新邏輯。例如,下面的 TaskApp 組件有一個(gè)數(shù)組類型的狀態(tài) tasks,并通過三個(gè)不同的事件處理程序來實(shí)現(xiàn)任務(wù)的添加、刪除和修改

import { useState } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';export default function TaskApp() {const [tasks, setTasks] = useState(initialTasks);function handleAddTask(text) {setTasks([...tasks,{id: nextId++,text: text,done: false,},]);}function handleChangeTask(task) {setTasks(tasks.map((t) => {if (t.id === task.id) {return task;} else {return t;}}));}function handleDeleteTask(taskId) {setTasks(tasks.filter((t) => t.id !== taskId));}return (<><h1>布拉格的行程安排</h1><AddTask onAddTask={handleAddTask} /><TaskListtasks={tasks}onChangeTask={handleChangeTask}onDeleteTask={handleDeleteTask}/></>);
}let nextId = 3;
const initialTasks = [{id: 0, text: '參觀卡夫卡博物館', done: true},{id: 1, text: '看木偶戲', done: false},{id: 2, text: '打卡列儂墻', done: false},
];

你可以通過三個(gè)步驟將 useState 遷移到 useReducer

第 1 步: 將設(shè)置狀態(tài)的邏輯修改成 dispatch 的一個(gè) action

移除所有的狀態(tài)設(shè)置邏輯。只留下三個(gè)事件處理函數(shù):

  • handleAddTask(text) 在用戶點(diǎn)擊 “添加” 時(shí)被調(diào)用。
  • handleChangeTask(task) 在用戶切換任務(wù)或點(diǎn)擊 “保存” 時(shí)被調(diào)用。
  • handleDeleteTask(taskId) 在用戶點(diǎn)擊 “刪除” 時(shí)被調(diào)用。

使用 reducers 管理狀態(tài)與直接設(shè)置狀態(tài)略有不同。它不是通過設(shè)置狀態(tài)來告訴 React “要做什么”,而是通過事件處理程序 dispatch 一個(gè) “action” 來指明 “用戶剛剛做了什么”。(而狀態(tài)更新邏輯則保存在其他地方!)因此,我們不再通過事件處理器直接 “設(shè)置 task”,而是 dispatch 一個(gè) “添加/修改/刪除任務(wù)” 的 action。這更加符合用戶的思維。

function handleAddTask(text) {dispatch({type: 'added',id: nextId++,text: text,});
}function handleChangeTask(task) {dispatch({type: 'changed',task: task,});
}function handleDeleteTask(taskId) {dispatch({type: 'deleted',id: taskId,});
}

第 2 步: 編寫一個(gè) reducer 函數(shù)

reducer 函數(shù)就是你放置狀態(tài)邏輯的地方。它接受兩個(gè)參數(shù),分別為當(dāng)前 state 和 action 對(duì)象,并且返回的是更新后的 state
在這個(gè)例子中,要將狀態(tài)設(shè)置邏輯從事件處理程序移到 reducer 函數(shù)中,你需要:

  1. 聲明當(dāng)前狀態(tài)(tasks)作為第一個(gè)參數(shù);
  2. 聲明 action 對(duì)象作為第二個(gè)參數(shù);
  3. 從 reducer 返回 下一個(gè) 狀態(tài)(React 會(huì)將舊的狀態(tài)設(shè)置為這個(gè)最新的狀態(tài))
function tasksReducer(tasks, action) {if (action.type === 'added') {return [...tasks,{id: action.id,text: action.text,done: false,},];} else if (action.type === 'changed') {return tasks.map((t) => {if (t.id === action.task.id) {return action.task;} else {return t;}});} else if (action.type === 'deleted') {return tasks.filter((t) => t.id !== action.id);} else {throw Error('未知 action: ' + action.type);}
}

上面的代碼使用了 if/else 語句,但是在 reducers 中使用 switch 語句 是一種慣例。兩種方式結(jié)果是相同的,但 switch 語句讀起來一目了然

function tasksReducer(tasks, action) {switch (action.type) {case 'added': {return [...tasks,{id: action.id,text: action.text,done: false,},];}case 'changed': {return tasks.map((t) => {if (t.id === action.task.id) {return action.task;} else {return t;}});}case 'deleted': {return tasks.filter((t) => t.id !== action.id);}default: {throw Error('未知 action: ' + action.type);}}
}

Reduce()
reduce() 是 JavaScript 中數(shù)組的高階函數(shù)之一,用于將數(shù)組中的元素歸納為單個(gè)值。它接受一個(gè)回調(diào)函數(shù)作為參數(shù),這個(gè)回調(diào)函數(shù)可以進(jìn)行累積操作。

語法

array.reduce(callback(accumulator, currentValue, currentIndex, array), initialValue);
  • callback:用于處理數(shù)組中每個(gè)元素的回調(diào)函數(shù),包含四個(gè)參數(shù):
    • accumulator:累積器,累積計(jì)算的結(jié)果。
    • currentValue:當(dāng)前處理的元素。
    • currentIndex(可選):當(dāng)前處理的元素的索引。
    • array(可選):調(diào)用 reduce 的數(shù)組。
  • initialValue(可選):作為第一次調(diào)用 callback 時(shí)的第一個(gè)參數(shù) accumulator 的初始值。如果不提供,將使用數(shù)組的第一個(gè)元素作為初始值,且 callback 不會(huì)在數(shù)組的第一個(gè)元素上調(diào)用。

示例

// 計(jì)算數(shù)組元素的和
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
console.log(sum); // 輸出 15// 找出數(shù)組中的最大值
const max = numbers.reduce((accumulator, currentValue) => Math.max(accumulator, currentValue));
console.log(max); // 輸出 5

使用場景

  • 累加或累乘:計(jì)算數(shù)組中所有元素的和或積。
  • 查找最值:找出數(shù)組中的最大值或最小值。
  • 轉(zhuǎn)換數(shù)據(jù)格式:將數(shù)組轉(zhuǎn)換成其他數(shù)據(jù)結(jié)構(gòu)。
  • 歸納操作:進(jìn)行復(fù)雜的歸納計(jì)算。

注意事項(xiàng)

  • 在沒有提供 initialValue 的情況下,reduce 將從數(shù)組的第二個(gè)元素開始調(diào)用回調(diào)函數(shù)。
  • 如果數(shù)組為空且沒有提供 initialValuereduce 將拋出 TypeError。
  • 回調(diào)函數(shù)在數(shù)組有元素時(shí)至少會(huì)被調(diào)用一次。
  • reduce 不會(huì)改變原數(shù)組。

示例

// 使用 reduce 計(jì)算數(shù)組中的平均值
const numbers = [10, 20, 30, 40, 50];
const average = numbers.reduce((accumulator, currentValue, index, array) => {accumulator += currentValue;if (index === array.length - 1) {return accumulator / array.length;} else {return accumulator;}
}, 0);console.log(average); // 輸出 30

在這個(gè)示例中,reduce 被用于計(jì)算數(shù)組 numbers 中所有元素的平均值。

第 3 步: 在組件中使用 reducer

你需要將 tasksReducer 導(dǎo)入到組件中。記得先從 React 中導(dǎo)入 useReducer Hook:

import { useReducer } from 'react';

接下來,你就可以替換掉之前的 useState:

const [tasks, setTasks] = useState(initialTasks);

只需要像下面這樣使用 useReducer:

const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);

useReducer 和 useState 很相似——你必須給它傳遞一個(gè)初始狀態(tài),它會(huì)返回一個(gè)有狀態(tài)的值和一個(gè)設(shè)置該狀態(tài)的函數(shù)(在這個(gè)例子中就是 dispatch 函數(shù))。但是,它們兩個(gè)之間還是有點(diǎn)差異的。
useReducer 鉤子接受 2 個(gè)參數(shù):

  1. 一個(gè) reducer 函數(shù)
  2. 一個(gè)初始的 state

它返回如下內(nèi)容:

  1. 一個(gè)有狀態(tài)的值
  2. 一個(gè) dispatch 函數(shù)(用來 “派發(fā)” 用戶操作給 reducer)
import { useReducer } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';export default function TaskApp() {const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);function handleAddTask(text) {dispatch({type: 'added',id: nextId++,text: text,});}function handleChangeTask(task) {dispatch({type: 'changed',task: task,});}function handleDeleteTask(taskId) {dispatch({type: 'deleted',id: taskId,});}return (<><h1>布拉格的行程安排</h1><AddTask onAddTask={handleAddTask} /><TaskListtasks={tasks}onChangeTask={handleChangeTask}onDeleteTask={handleDeleteTask}/></>);
}function tasksReducer(tasks, action) {switch (action.type) {case 'added': {return [...tasks,{id: action.id,text: action.text,done: false,},];}case 'changed': {return tasks.map((t) => {if (t.id === action.task.id) {return action.task;} else {return t;}});}case 'deleted': {return tasks.filter((t) => t.id !== action.id);}default: {throw Error('未知 action: ' + action.type);}}
}let nextId = 3;
const initialTasks = [{id: 0, text: '參觀卡夫卡博物館', done: true},{id: 1, text: '看木偶戲', done: false},{id: 2, text: '打卡列儂墻', done: false}
];

對(duì)比 useState 和 useReducer

  • 代碼體積: 通常,在使用 useState 時(shí),一開始只需要編寫少量代碼。而 useReducer 必須提前編寫 reducer 函數(shù)和需要調(diào)度的 actions。但是,當(dāng)多個(gè)事件處理程序以相似的方式修改 state 時(shí),useReducer 可以減少代碼量。
  • 可讀性: 當(dāng)狀態(tài)更新邏輯足夠簡單時(shí),useState 的可讀性還行。但是,一旦邏輯變得復(fù)雜起來,它們會(huì)使組件變得臃腫且難以閱讀。在這種情況下,useReducer 允許你將狀態(tài)更新邏輯與事件處理程序分離開來。
  • 可調(diào)試性: 當(dāng)使用 useState 出現(xiàn)問題時(shí), 你很難發(fā)現(xiàn)具體原因以及為什么。 而使用 useReducer 時(shí), 你可以在 reducer 函數(shù)中通過打印日志的方式來觀察每個(gè)狀態(tài)的更新,以及為什么要更新(來自哪個(gè) action)。 如果所有 action 都沒問題,你就知道問題出在了 reducer 本身的邏輯中。 然而,與使用 useState 相比,你必須單步執(zhí)行更多的代碼。
  • 可測試性: reducer 是一個(gè)不依賴于組件的純函數(shù)。這就意味著你可以單獨(dú)對(duì)它進(jìn)行測試。一般來說,我們最好是在真實(shí)環(huán)境中測試組件,但對(duì)于復(fù)雜的狀態(tài)更新邏輯,針對(duì)特定的初始狀態(tài)和 action,斷言 reducer 返回的特定狀態(tài)會(huì)很有幫助。
  • 個(gè)人偏好: 并不是所有人都喜歡用 reducer,沒關(guān)系,這是個(gè)人偏好問題。你可以隨時(shí)在 useState 和 useReducer 之間切換,它們能做的事情是一樣的

練習(xí)1,2,3,4 暫時(shí)擱置

使用 Context 深層傳遞參數(shù)

通常來說,你會(huì)通過 props 將信息從父組件傳遞到子組件。但是,如果你必須通過許多中間組件向下傳遞 props,或是在你應(yīng)用中的許多組件需要相同的信息,傳遞 props 會(huì)變的十分冗長和不便。Context 允許父組件向其下層無論多深的任何組件提供信息,而無需通過 props 顯式傳遞

傳遞 props 帶來的問題

image.png

Context:傳遞 props 的另一種方法

Context 讓父組件可以為它下面的整個(gè)組件樹提供數(shù)據(jù)
你可以通過以下三個(gè)步驟來實(shí)現(xiàn)它:

  1. 創(chuàng)建 一個(gè) context。(你可以將其命名為 LevelContext, 因?yàn)樗硎镜氖菢?biāo)題級(jí)別。)
  2. 在需要數(shù)據(jù)的組件內(nèi) 使用 剛剛創(chuàng)建的 context。(Heading 將會(huì)使用 LevelContext。)
  3. 在指定數(shù)據(jù)的組件中 提供 這個(gè) context。 (Section 將會(huì)提供 LevelContext。

image.png

寫在你使用 context 之前

使用 Context 看起來非常誘人!然而,這也意味著它也太容易被過度使用了。如果你只想把一些 props 傳遞到多個(gè)層級(jí)中,這并不意味著你需要把這些信息放到 context 里
在使用 context 之前,你可以考慮以下幾種替代方案:

  1. 傳遞 props 開始。 如果你的組件看起來不起眼,那么通過十幾個(gè)組件向下傳遞一堆 props 并不罕見。這有點(diǎn)像是在埋頭苦干,但是這樣做可以讓哪些組件用了哪些數(shù)據(jù)變得十分清晰!維護(hù)你代碼的人會(huì)很高興你用 props 讓數(shù)據(jù)流變得更加清晰。
  2. 抽象組件并 將 JSX 作為children傳遞 給它們。 如果你通過很多層不使用該數(shù)據(jù)的中間組件(并且只會(huì)向下傳遞)來傳遞數(shù)據(jù),這通常意味著你在此過程中忘記了抽象組件。舉個(gè)例子,你可能想傳遞一些像 posts 的數(shù)據(jù) props 到不會(huì)直接使用這個(gè)參數(shù)的組件,類似 。取而代之的是,讓 Layout 把 children 當(dāng)做一個(gè)參數(shù),然后渲染 。這樣就減少了定義數(shù)據(jù)的組件和使用數(shù)據(jù)的組件之間的層級(jí)

Context 的使用場景

  • 主題: 如果你的應(yīng)用允許用戶更改其外觀(例如暗夜模式),你可以在應(yīng)用頂層放一個(gè) context provider,并在需要調(diào)整其外觀的組件中使用該 context。
  • 當(dāng)前賬戶: 許多組件可能需要知道當(dāng)前登錄的用戶信息。將它放到 context 中可以方便地在樹中的任何位置讀取它。某些應(yīng)用還允許你同時(shí)操作多個(gè)賬戶(例如,以不同用戶的身份發(fā)表評(píng)論)。在這些情況下,將 UI 的一部分包裹到具有不同賬戶數(shù)據(jù)的 provider 中會(huì)很方便。
  • 路由: 大多數(shù)路由解決方案在其內(nèi)部使用 context 來保存當(dāng)前路由。這就是每個(gè)鏈接“知道”它是否處于活動(dòng)狀態(tài)的方式。如果你創(chuàng)建自己的路由庫,你可能也會(huì)這么做。
  • 狀態(tài)管理: 隨著你的應(yīng)用的增長,最終在靠近應(yīng)用頂部的位置可能會(huì)有很多 state。許多遙遠(yuǎn)的下層組件可能想要修改它們。通常 將 reducer 與 context 搭配使用來管理復(fù)雜的狀態(tài)并將其傳遞給深層的組件來避免過多的麻煩

傳遞 Context 的方法:

  1. 通過 export const MyContext = createContext(defaultValue) 創(chuàng)建并導(dǎo)出 context。
  2. 在無論層級(jí)多深的任何子組件中,把 context 傳遞給 useContext(MyContext) Hook 來讀取它。
  3. 在父組件中把 children 包在 <MyContext.Provider value={…}> 中來提供 context。

使用 Reducer 和 Context 拓展你的應(yīng)用

下面將介紹如何結(jié)合使用 reducer 和 context:

  1. 創(chuàng)建 context。
  2. 將 state 和 dispatch 放入 context。
  3. 在組件樹的任何地方 使用 context

應(yīng)急方案

使用 ref 引用值

當(dāng)你希望組件“記住”某些信息,但又不想讓這些信息 觸發(fā)新的渲染 時(shí),你可以使用 ref 。

給你的組件添加 ref

你可以通過從 React 導(dǎo)入 useRef Hook 來為你的組件添加一個(gè) ref:

import { useRef } from 'react';

在你的組件內(nèi),調(diào)用 useRef Hook 并傳入你想要引用的初始值作為唯一參數(shù)。例如,這里的 ref 引用的值是“0”:

const ref = useRef(0);

useRef 返回一個(gè)這樣的對(duì)象:

{ current: 0 // 你向 useRef 傳入的值}
import { useRef } from 'react';export default function Counter() {let ref = useRef(0);function handleClick() {ref.current = ref.current + 1;alert('你點(diǎn)擊了 ' + ref.current + ' 次!');}return (<button onClick={handleClick}>點(diǎn)擊我!</button>);
}

組件不會(huì)在每次遞增時(shí)重新渲染。 與 state 一樣,React 會(huì)在每次重新渲染之間保留 ref。但是,設(shè)置 state 會(huì)重新渲染組件,更改 ref 不會(huì)!

ref 和 state 的不同之處

image.png

useRef 內(nèi)部是如何運(yùn)行的?

盡管 useState 和 useRef 都是由 React 提供的,原則上 useRef 可以在 useState的基礎(chǔ)上 實(shí)現(xiàn)。 你可以想象在 React 內(nèi)部,useRef 是這樣實(shí)現(xiàn)的:

// React 內(nèi)部function useRef(initialValue) {const [ref, unused] = useState({ current: initialValue });return ref;}

何時(shí)使用 ref

  • 存儲(chǔ) timeout ID
  • 存儲(chǔ)和操作 DOM 元素,我們將在 下一頁 中介紹
  • 存儲(chǔ)不需要被用來計(jì)算 JSX 的其他對(duì)象

ref 的最佳實(shí)踐

遵循這些原則將使你的組件更具可預(yù)測性:

  • 將 ref 視為應(yīng)急方案。 當(dāng)你使用外部系統(tǒng)或?yàn)g覽器 API 時(shí),ref 很有用。如果你很大一部分應(yīng)用程序邏輯和數(shù)據(jù)流都依賴于 ref,你可能需要重新考慮你的方法。
  • 不要在渲染過程中讀取或?qū)懭?ref.current。 如果渲染過程中需要某些信息,請(qǐng)使用 state 代替。由于 React 不知道 ref.current 何時(shí)發(fā)生變化,即使在渲染時(shí)讀取它也會(huì)使組件的行為難以預(yù)測。(唯一的例外是像 if (!ref.current) ref.current = new Thing() 這樣的代碼,它只在第一次渲染期間設(shè)置一次 ref。)

使用 ref 操作 DOM

獲取指向節(jié)點(diǎn)的 ref

要訪問由 React 管理的 DOM 節(jié)點(diǎn),首先,引入 useRef Hook:

import { useRef } from 'react';

然后,在你的組件中使用它聲明一個(gè) ref:

const myRef = useRef(null);

Finally, pass your ref as the ref attribute to the JSX tag for which you want to get the DOM node:

<div ref={myRef}>

useRef Hook 返回一個(gè)對(duì)象,該對(duì)象有一個(gè)名為 current 的屬性。最初,myRef.current 是 null。當(dāng) React 為這個(gè)

創(chuàng)建一個(gè) DOM 節(jié)點(diǎn)時(shí),React 會(huì)把對(duì)該節(jié)點(diǎn)的引用放入 myRef.current。然后,你可以從 事件處理器 訪問此 DOM 節(jié)點(diǎn),并使用在其上定義的內(nèi)置 瀏覽器 API。

// 你可以使用任意瀏覽器 API,例如:myRef.current.scrollIntoView();

在上面的例子中,ref 的數(shù)量是預(yù)先確定的。但有時(shí)候,你可能需要為列表中的每一項(xiàng)都綁定 ref ,而你又不知道會(huì)有多少項(xiàng)。像下面這樣做是行不通的

<ul>{items.map((item) => {// 行不通!const ref = useRef(null);return <li ref={ref} />;})}</ul>

這是因?yàn)?Hook 只能在組件的頂層被調(diào)用。不能在循環(huán)語句、條件語句或 map() 函數(shù)中調(diào)用 useRef

訪問另一個(gè)組件的 DOM 節(jié)點(diǎn)

默認(rèn)情況下,React 不允許組件訪問其他組件的 DOM 節(jié)點(diǎn)。甚至自己的子組件也不行
想要 暴露其 DOM 節(jié)點(diǎn)的組件必須選擇該行為。一個(gè)組件可以指定將它的 ref “轉(zhuǎn)發(fā)”給一個(gè)子組件。下面是 MyInput 如何使用 forwardRef API:

const MyInput = forwardRef((props, ref) => {return <input {...props} ref={ref} />;});

使用 Effect 同步

有些組件需要與外部系統(tǒng)同步。例如,你可能希望根據(jù) React state 控制非 React 組件、設(shè)置服務(wù)器連接或在組件出現(xiàn)在屏幕上時(shí)發(fā)送分析日志。Effects 會(huì)在渲染后運(yùn)行一些代碼,以便可以將組件與 React 之外的某些系統(tǒng)同步
在本文和后續(xù)文本中,Effect 在 React 中是專有定義——由渲染引起的副作用。為了指代更廣泛的編程概念,也可以將其稱為“副作用(side effect)

不要隨意在你的組件中使用 Effect。記住,Effect 通常用于暫時(shí)“跳出” React 代碼并與一些 外部 系統(tǒng)進(jìn)行同步。這包括瀏覽器 API、第三方小部件,以及網(wǎng)絡(luò)等等。如果你想用 Effect 僅根據(jù)其他狀態(tài)調(diào)整某些狀態(tài),那么 你可能不需要 Effect。

如何編寫 Effect

第一步:聲明 Effect

首先在 React 中引入 useEffect Hook:

import { useEffect } from 'react';

然后,在組件頂部調(diào)用它,并傳入在每次渲染時(shí)都需要執(zhí)行的代碼:

function MyComponent() {useEffect(() => {// 每次渲染后都會(huì)執(zhí)行此處的代碼});return <div />;}

一般來說,Effect 會(huì)在 每次 渲染后執(zhí)行,而以下代碼會(huì)陷入死循環(huán)中

const [count, setCount] = useState(0);useEffect(() => {setCount(count + 1);});

每次渲染結(jié)束都會(huì)執(zhí)行 Effect;而更新 state 會(huì)觸發(fā)重新渲染。但是新一輪渲染時(shí)又會(huì)再次執(zhí)行 Effect,然后 Effect 再次更新 state……如此周而復(fù)始,從而陷入死循環(huán)

第二步:指定 Effect 依賴

一般來說,Effect 會(huì)在 每次 渲染時(shí)執(zhí)行。但更多時(shí)候,并不需要每次渲染的時(shí)候都執(zhí)行 Effect。

  • 有時(shí)這會(huì)拖慢運(yùn)行速度。因?yàn)榕c外部系統(tǒng)的同步操作總是有一定時(shí)耗,在非必要時(shí)可能希望跳過它。例如,沒有人會(huì)希望每次用鍵盤打字時(shí)都重新連接聊天服務(wù)器。
  • 有時(shí)這會(huì)導(dǎo)致程序邏輯錯(cuò)誤。例如,組件的淡入動(dòng)畫只需要在第一輪渲染出現(xiàn)時(shí)播放一次,而不是每次觸發(fā)新一輪渲染后都播放。
 useEffect(() => {if (isPlaying) { // isPlaying 在此處使用……// ...} else {// ...}}, [isPlaying]); // ……所以它必須在此處聲明!

指定 [isPlaying] 會(huì)告訴 React,如果 isPlaying 在上一次渲染時(shí)與當(dāng)前相同,它應(yīng)該跳過重新運(yùn)行 Effect
沒有依賴數(shù)組作為第二個(gè)參數(shù),與依賴數(shù)組位空數(shù)組 [] 的行為是不一致的:

useEffect(() => {// 這里的代碼會(huì)在每次渲染后執(zhí)行});useEffect(() => {// 這里的代碼只會(huì)在組件掛載后執(zhí)行}, []);useEffect(() => {//這里的代碼只會(huì)在每次渲染后,并且 a 或 b 的值與上次渲染不一致時(shí)執(zhí)行}, [a, b]);

第三部:按需添加清理(cleanup)函數(shù)

可以在 Effect 中返回一個(gè) 清理(cleanup) 函數(shù)。useEffect(() => {const connection = createConnection();connection.connect();return () => {connection.disconnect();};}, []);


每次重新執(zhí)行 Effect 之前,React 都會(huì)調(diào)用清理函數(shù);組件被卸載時(shí),也會(huì)調(diào)用清理函數(shù)

訂閱事件

如果 Effect 訂閱了某些事件,清理函數(shù)應(yīng)該退訂這些事件:

useEffect(() => {function handleScroll(e) {console.log(window.scrollX, window.scrollY);}window.addEventListener('scroll', handleScroll);return () => window.removeEventListener('scroll', handleScroll);}, []);

觸發(fā)動(dòng)畫

如果 Effect 對(duì)某些內(nèi)容加入了動(dòng)畫,清理函數(shù)應(yīng)將動(dòng)畫重置:

useEffect(() => {const node = ref.current;node.style.opacity = 1; // 觸發(fā)動(dòng)畫return () => {node.style.opacity = 0; // 重置為初始值};}, []);

初始化應(yīng)用時(shí)不需要使用 Effect 的情形

某些邏輯應(yīng)該只在應(yīng)用程序啟動(dòng)時(shí)運(yùn)行一次。比如,驗(yàn)證登陸狀態(tài)和加載本地程序數(shù)據(jù)。你可以將其放在組件之外:

if (typeof window !== 'undefined') { // 檢查是否在瀏覽器中運(yùn)行checkAuthToken();loadDataFromLocalStorage();}function App() {// ……}

你可能不需要 Effect

Effect 是 React 范式中的一個(gè)逃脫方案。它們讓你可以 “逃出” React 并使組件和一些外部系統(tǒng)同步,比如非 React 組件、網(wǎng)絡(luò)和瀏覽器 DOM。如果沒有涉及到外部系統(tǒng)(例如,你想根據(jù) props 或 state 的變化來更新一個(gè)組件的 state),你就不應(yīng)該使用 Effect

響應(yīng)式 Effect 的生命周期

Effect 的生命周期

每個(gè) React 組件都經(jīng)歷相同的生命周期:

  • 當(dāng)組件被添加到屏幕上時(shí),它會(huì)進(jìn)行組件的 掛載。
  • 當(dāng)組件接收到新的 props 或 state 時(shí),通常是作為對(duì)交互的響應(yīng),它會(huì)進(jìn)行組件的 更新。
  • 當(dāng)組件從屏幕上移除時(shí),它會(huì)進(jìn)行組件的 卸載
http://aloenet.com.cn/news/41207.html

相關(guān)文章:

  • 如何寫一個(gè)可以做報(bào)價(jià)計(jì)算的網(wǎng)站網(wǎng)絡(luò)服務(wù)網(wǎng)絡(luò)推廣
  • 為什么自己做的網(wǎng)站別的電腦打不開廣州新聞最新消息今天
  • 怎么做游戲自動(dòng)充值的網(wǎng)站重慶高端網(wǎng)站seo
  • 信息化平臺(tái)的功能介紹搜索引擎優(yōu)化 簡歷
  • 深圳住房和建設(shè)局網(wǎng)站輪候大廳網(wǎng)絡(luò)營銷屬于什么專業(yè)類型
  • 移動(dòng)端使用wordpress富文本編輯器便宜的seo官網(wǎng)優(yōu)化
  • 全自動(dòng)網(wǎng)站建設(shè)最新實(shí)時(shí)大數(shù)據(jù)
  • wordpress好用還是dede磐石網(wǎng)站seo
  • 上海高端定制網(wǎng)站公司關(guān)鍵詞優(yōu)化的技巧
  • 門戶網(wǎng)登錄入口seo軟件定制
  • wordpress視覺編輯器seo在線優(yōu)化工具
  • 上海網(wǎng)站公司電話seo推廣是什么意思
  • 權(quán)大師的網(wǎng)站是哪個(gè)公司做的香港疫情最新消息
  • 網(wǎng)站建設(shè)為中心百度關(guān)鍵詞收錄
  • 個(gè)人主頁自助建站搜索引擎的工作原理分為
  • 孝感 網(wǎng)站建設(shè)百度電商廣告代運(yùn)營
  • 棗莊專業(yè)做網(wǎng)站競價(jià)廣告代運(yùn)營
  • 網(wǎng)站域名個(gè)人備案查詢推廣平臺(tái)排行榜app
  • 國外超酷設(shè)計(jì)網(wǎng)站游戲推廣
  • 如何做服裝的微商城網(wǎng)站重慶森林經(jīng)典臺(tái)詞獨(dú)白
  • 云南省建設(shè)工程招標(biāo)投標(biāo)行業(yè)協(xié)會(huì)網(wǎng)站百度seo排名優(yōu)化教程
  • vs2010做網(wǎng)站時(shí)間控件廊坊網(wǎng)站排名優(yōu)化公司哪家好
  • readme.md做網(wǎng)站seo平臺(tái)是什么意思
  • 哪個(gè)網(wǎng)站可以做優(yōu)惠券seo技術(shù)培訓(xùn)寧波
  • qq是哪個(gè)公司開發(fā)seo排名平臺(tái)
  • 特價(jià)手機(jī)網(wǎng)站建設(shè)1688seo優(yōu)化是什么
  • 網(wǎng)頁設(shè)計(jì)實(shí)訓(xùn)報(bào)告實(shí)訓(xùn)小結(jié)深圳百度seo整站
  • 網(wǎng)站logo怎么做最清楚惠州網(wǎng)站制作推廣
  • 廈門app網(wǎng)站建設(shè)平臺(tái)推廣是什么工作
  • 建站之星如何建網(wǎng)站sem推廣是什么