成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一個(gè)新的React概念:Effect Event

開(kāi)發(fā) 前端
在React中,有一個(gè)「非常容易」被誤用的API —— UseEffect,今天要介紹的Effect Event就屬于由useEffect衍生出的概念。首先來(lái)聊聊Event與Effect。useEffect容易被誤用也是因?yàn)檫@兩個(gè)概念很容易混淆。

大家好,我卡頌。

每個(gè)框架由于實(shí)現(xiàn)原理的區(qū)別,都會(huì)有些獨(dú)特的概念。比如:

  • Vue3由于其響應(yīng)式的實(shí)現(xiàn)原理,衍生出ref、reactive等概念。
  • Svelte重度依賴自身的編譯器,所以衍生出與編譯相關(guān)的概念(比如其對(duì)label標(biāo)簽的創(chuàng)新性使用)。

在React中,有一個(gè)「非常容易」被誤用的API —— useEffect,今天要介紹的Effect Event就屬于由useEffect衍生出的概念。

被誤用的useEffect

本文一共會(huì)涉及三個(gè)概念:

  • Event(事件)
  • Effect(副作用)
  • Effect Event(副作用事件)

首先來(lái)聊聊Event與Effect。useEffect容易被誤用也是因?yàn)檫@兩個(gè)概念很容易混淆。

Event的概念

在下面的代碼中,點(diǎn)擊div會(huì)觸發(fā)點(diǎn)擊事件,onClick是點(diǎn)擊回調(diào)。其中onClick就屬于Event:

function App() {
  const [num , update] = useState(0);
  
  function onClick() {
    update(num + 1);
  }
  
  return (
    <div onClick={onClick}>{num}</div>
  )
}

Event的特點(diǎn)是:「是由某些行為觸發(fā),而不是狀態(tài)變化觸發(fā)的邏輯」。

比如,在上述代碼中,onClick是由「點(diǎn)擊事件」這一行為觸發(fā)的邏輯,num狀態(tài)變化不會(huì)觸發(fā)onClick。

Effect的概念

Effect則與Event相反,他是「由某些狀態(tài)變化觸發(fā)的,而不是某些行為觸發(fā)的邏輯」。

比如,在下述代碼中,當(dāng)title變化后document.title會(huì)更新為title的值:

function Title({title}) {
  useEffect(() => {
    document.title = title;
  }, [title])
  
  // ...
}

上述代碼中useEffect的邏輯就屬于Effect,他是由title變化觸發(fā)的。除了useEffect外,下面兩個(gè)Hook也屬于Effect:

  • useLayoutEffect(不常用)
  • useInsertionEffect(很不常用)

為什么容易誤用?

現(xiàn)在問(wèn)題來(lái)了:Event與Effect的概念完全不同,為什么會(huì)被誤用?

舉個(gè)例子,在項(xiàng)目的第一個(gè)版本中,我們?cè)趗seEffect中有個(gè)初始化數(shù)據(jù)的邏輯:

function App() {
  const [data, updateData] = useState(null);

  useEffect(() => {
    fetchData().then(data => {
      // ...一些業(yè)務(wù)邏輯
      // 更新data
      updateData(data);
    })
  }, []);

  // ...
}

隨著項(xiàng)目發(fā)展,你又接到一個(gè)需求:提交表單后更新數(shù)據(jù)。

為了復(fù)用之前的邏輯,你新增了options狀態(tài)(保存表單數(shù)據(jù)),并將他作為useEffect的依賴:

function App() {
  const [data, updateData] = useState(null);
  const [options, updateOptions] = useState(null);

  useEffect(() => {
    fetchData(options).then(data => {
      // ...一些業(yè)務(wù)邏輯
      // 更新data
      updateData(data);
    })
  }, [options]);
  
  
  function onSubmit(opt) {
    updateOptions(opt);
  }

  // ...
}

現(xiàn)在,提交表單后(觸發(fā)onSubmit回調(diào))就能復(fù)用之前的數(shù)據(jù)初始化邏輯。

這么做實(shí)在是方便,以至于很多同學(xué)認(rèn)為這就是useEffect的用法。但其實(shí)這是典型的「useEffect誤用」。

仔細(xì)分析我們會(huì)發(fā)現(xiàn):「提交表單」顯然是個(gè)Event(由提交的行為觸發(fā)),Event的邏輯應(yīng)該寫(xiě)在事件回調(diào)中,而不是useEffect中。正確的寫(xiě)法應(yīng)該是這樣:

function App() {
  const [data, updateData] = useState(null);

  useEffect(() => {
    fetchData().then(data => {
      // ...一些業(yè)務(wù)邏輯
      // 更新data
      updateData(data);
    })
  }, []);
  
  
  function onSubmit(opt) {
    fetchData(opt).then(data => {
      // ...一些業(yè)務(wù)邏輯
      // 更新data
      updateData(data);
    })
  }

  // ...
}

上述例子邏輯比較簡(jiǎn)單,兩種寫(xiě)法的區(qū)別不大。但在實(shí)際項(xiàng)目中,隨著項(xiàng)目不斷迭代,可能出現(xiàn)如下代碼:

useEffect(() => {
  fetchData(options).then(data => {
    // ...一些業(yè)務(wù)邏輯
    // 更新data
    updateData(data);
  })
}, [options, xxx, yyy, zzz]);

屆時(shí),很難清楚fetchData方法會(huì)在什么情況下執(zhí)行,因?yàn)椋?/p>

  1. useEffect的依賴項(xiàng)太多了
  2. 很難完全掌握每個(gè)依賴項(xiàng)變化的時(shí)機(jī)

所以,在React中,我們需要清楚的區(qū)分Event與Effect,也就是清楚的區(qū)分「一段邏輯是由行為觸發(fā)的,還是狀態(tài)變化觸發(fā)的?」

useEffect的依賴問(wèn)題

現(xiàn)在,我們已經(jīng)能清楚的區(qū)分Event與Effect,按理說(shuō)寫(xiě)項(xiàng)目不會(huì)有問(wèn)題了。但是,由于「Effect的機(jī)制問(wèn)題」,我們還面臨一個(gè)新問(wèn)題。

假設(shè)我們有段聊天室代碼,當(dāng)roomId變化后,要重新連接到新聊天室。在這個(gè)場(chǎng)景下,聊天室的斷開(kāi)/重新連接依賴于roomId狀態(tài)的變化,顯然屬于Effect,代碼如下:

function ChatRoom({roomId}) {
  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();
    
    return () => {
      connection.disconnect()
    };
  }, [roomId]);
  
  // ...
}

接下來(lái)你接到了新需求 —— 當(dāng)連接成功后,彈出「全局提醒」:

「全局提醒」是否是黑暗模式,受到theme props影響。useEffect修改后的代碼如下:

useEffect(() => {
  const connection = createConnection(roomId);
  connection.connect();
  
  connection.on('connected', () => {
    showNotification('連接成功!', theme);
  });
  
  return () => connection.disconnect();
}, [roomId, theme]);

但這段代碼有個(gè)嚴(yán)重問(wèn)題 —— 任何導(dǎo)致theme變化的情況都會(huì)導(dǎo)致聊天室斷開(kāi)/重新連接。畢竟,theme也是useEffect的依賴項(xiàng)。

在這個(gè)例子中,雖然Effect依賴theme,但Effect并不是由theme變化而觸發(fā)的(他是由roomId變化觸發(fā)的)。

為了應(yīng)對(duì)這種場(chǎng)景,React提出了一個(gè)新概念 —— Effect Event。他指那些「在Effect內(nèi)執(zhí)行,但Effect并不依賴其中狀態(tài)的邏輯」,比如上例中的:

() => {
  showNotification('連接成功!', theme);
}

我們可以使用useEffectEvent(這是個(gè)試驗(yàn)性Hook)定義Effect Event:

function ChatRoom({roomId, theme}) {
  const onConnected = useEffectEvent(() => {
    showNotification('連接成功!', theme);
  });

  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();
    
    connection.on('connected', () => {
      onConnected();
    });
    
    return () => {
      connection.disconnect()
    };
  }, [roomId]);
  
  // ...
}

在上面代碼中,theme被移到onConnected(他是個(gè)Effect Event)中,useEffect雖然使用了theme的最新值,但并不需要將他作為依賴。

useEffectEvent源碼解析

useEffectEvent的實(shí)現(xiàn)并不復(fù)雜,核心代碼如下:

function updateEvent(callback) {
  const hook = updateWorkInProgressHook();
  // 保存callback的引用
  const ref = hook.memoizedState;
  // 在useEffect執(zhí)行前更新callback的引用
  useEffectEventImpl({ref, nextImpl: callback});
  
  return function eventFn() {
    if (isInvalidExecutionContextForEventFunction()) {
      throw new Error(
        "A function wrapped in useEffectEvent can't be called during rendering.",
      );
    }
    return ref.impl.apply(undefined, arguments);
  };
}

其中ref變量保存「callback的引用」。對(duì)于上述例子中:

const onConnected = useEffectEvent(() => {
  showNotification('連接成功!', theme);
});

ref保存對(duì)如下函數(shù)的引用:

() => {
  showNotification('連接成功!', theme);
}

useEffectEventImpl方法接受ref和callback的最新值為參數(shù),在useEffect執(zhí)行前會(huì)將ref中保存的callback引用更新為callback的最新值。

所以,當(dāng)在useEffect中執(zhí)行onConnected,獲取的就是ref中保存的下述閉包的最新值:

() => {
  showNotification('連接成功!', theme);
}

閉包中的theme自然也是最新值。

useEffectEvent與useEvent

仔細(xì)觀察下useEffectEvent的返回值,他包含了兩個(gè)限制:

return function eventFn() {
    if (isInvalidExecutionContextForEventFunction()) {
      throw new Error(
        "A function wrapped in useEffectEvent can't be called during rendering.",
      );
    }
    return ref.impl.apply(undefined, arguments);
};

第一個(gè)限制比較明顯 —— 下面這行代碼限制useEffectEvent的返回值只能在useEffect回調(diào)中執(zhí)行(否則會(huì)報(bào)錯(cuò)):

if (isInvalidExecutionContextForEventFunction()) {
  // ...    
}

另一個(gè)限制則比較隱晦 —— 返回值是個(gè)全新的引用:

return function eventFn() {
  // ...
};

如果你不太明白「全新的引用」為什么是個(gè)限制,考慮下返回一個(gè)useCallback返回值:

return useCallback((...args) => {
    const fn = ref.impl;
    return fn(...args);
}, []);

這將會(huì)讓useEffectEvent的返回值成為不變的引用,如果再去掉「只能在useEffect回調(diào)中執(zhí)行」的限制,那么useEffectEvent將是加強(qiáng)版的useCallback。

舉個(gè)例子,如果破除上述限制,那么對(duì)于下面的代碼:

function App({a, b}) {
  const [c, updateC] = useState(0);
  const fn = useCallback(() => a + b + c, [a, b, c])
  
  // ...
}

用useEffectEvent替代useCallback,代碼如下:

const fn = useEffectEvent(() => a + b + c)

相比于useCallback,他有2個(gè)優(yōu)點(diǎn):

  1. 不用顯式聲明依賴
  2. 即使依賴變了,fn的引用也不會(huì)變,簡(jiǎn)直是性能優(yōu)化的最佳選擇

那么React為什么要為useEffectEvent加上限制呢?

實(shí)際上,useEffectEvent的前身useEvent就是遵循上述實(shí)現(xiàn),但是由于:

  1. useEvent的定位應(yīng)該是Effect Event,但實(shí)際用途更廣(可以替代useCallback),這不符合他的定位
  2. 當(dāng)前React Forget(能生成等效于useMemo、useCallback代碼的官方編譯器)并未考慮useEvent,如果增加這個(gè)hook,會(huì)提高React Forget實(shí)現(xiàn)的難度

所以,useEvent并沒(méi)有正式進(jìn)入標(biāo)準(zhǔn)。相反,擁有更多限制的useEffectEvent反而進(jìn)入了React文檔[1]。

總結(jié)

今天我們學(xué)到三個(gè)概念:

  • Event:由某些行為觸發(fā),而不是狀態(tài)變化觸發(fā)的邏輯。
  • Effect:由某些狀態(tài)變化觸發(fā)的,而不是某些行為觸發(fā)的邏輯。
  • Effect Event:在Effect內(nèi)執(zhí)行,但Effect并不依賴其中狀態(tài)的邏輯。

其中Effect Event在React中的具體實(shí)現(xiàn)是useEffectEvent。相比于他的前身useEvent,他附加了2條限制:

  1. 只能在Effect內(nèi)執(zhí)行。
  2. 始終返回不同的引用。

在我看來(lái),Effect Event的出現(xiàn)完全是由于Hooks實(shí)現(xiàn)機(jī)制上的復(fù)雜性(必須顯式指明依賴)導(dǎo)致的心智負(fù)擔(dān)。

畢竟,同樣遵循Hooks理念的Vue Composition API就沒(méi)有這方面問(wèn)題。

參考資料

[1]React文檔:https://react.dev/learn/separating-events-from-effects。

責(zé)任編輯:姜華 來(lái)源: 魔術(shù)師卡頌
相關(guān)推薦

2022-06-05 21:27:40

Reacteffect

2011-12-14 15:53:51

云計(jì)算

2010-04-01 14:05:41

云計(jì)算

2009-10-01 09:19:45

PHP框架ZendFramewoCake

2009-10-20 14:10:00

CCIE經(jīng)驗(yàn)

2014-07-02 10:03:42

App推廣渠道

2021-06-21 15:49:39

React動(dòng)效組件

2024-03-19 00:00:00

ReactJavaScript開(kāi)發(fā)

2022-03-16 17:01:35

React18并發(fā)的React組件render

2016-08-04 14:59:56

分段路由器路由器

2023-01-30 09:01:34

DecoratorsJS語(yǔ)法

2009-04-20 23:29:12

Oracle收購(gòu)Sun甲骨文

2024-04-15 12:54:00

ReactVue列表邏輯

2015-08-06 17:15:28

2023-05-15 08:30:35

YjsReact

2022-10-29 08:55:19

頁(yè)面react

2022-06-06 09:28:36

ReactHook

2018-10-22 13:53:02

無(wú)人零售無(wú)人貨架智能

2014-06-20 10:42:27

Windows 8.1

2022-01-04 12:41:37

Collabora圖形后端Cairo
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 亚洲性在线 | 天天干 夜夜操 | 亚洲成人综合在线 | 国产精品精品视频一区二区三区 | 碰碰视频 | 视频一区二区三区四区五区 | aa级毛片毛片免费观看久 | 欧美成人一区二区 | 毛片视频免费 | 国产精品久久久久无码av | 久久久这里都是精品 | 一区二区日韩 | 久久www免费人成看片高清 | 人碰人操| 情侣酒店偷拍一区二区在线播放 | 午夜久久久久久久久久一区二区 | 免费成人高清在线视频 | 一区二区三区日韩精品 | 成人在线视频免费播放 | 一区二区三区高清 | 在线成人www免费观看视频 | 色久在线| 天天澡天天操 | 特级做a爱片免费69 精品国产鲁一鲁一区二区张丽 | 日韩欧美理论片 | 亚洲精品一区二区三区中文字幕 | 男人天堂av网站 | 99久久婷婷国产综合精品 | 国产女人与拘做受免费视频 | 日韩一区二区不卡 | 91在线观看免费 | h片在线看 | 一级毛片高清 | 久久精品毛片 | 国产一区视频在线 | 成人午夜电影网 | 美女张开腿露出尿口 | 99亚洲精品 | 国产精品久久久久av | 中文字幕一区二区三区不卡 | 国产天天操 |