自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

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

大家好,我卡頌。

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

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

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

被誤用的useEffect

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

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

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

Event的概念

在下面的代碼中,點(diǎn)擊div會觸發(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)變化不會觸發(fā)onClick。

Effect的概念

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

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

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

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

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

為什么容易誤用?

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

舉個(gè)例子,在項(xiàng)目的第一個(gè)版本中,我們在useEffect中有個(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ì)分析我們會發(fā)現(xiàn):「提交表單」顯然是個(gè)Event(由提交的行為觸發(fā)),Event的邏輯應(yīng)該寫在事件回調(diào)中,而不是useEffect中。正確的寫法應(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);
    })
  }

  // ...
}

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

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

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

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

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

useEffect的依賴問題

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

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

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

接下來你接到了新需求 —— 當(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)重問題 —— 任何導(dǎo)致theme變化的情況都會導(dǎo)致聊天室斷開/重新連接。畢竟,theme也是useEffect的依賴項(xiàng)。

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

為了應(yī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的引用」。對于上述例子中:

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

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

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

useEffectEventImpl方法接受ref和callback的最新值為參數(shù),在useEffect執(zhí)行前會將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í)行(否則會報(bào)錯(cuò)):

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

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

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

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

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

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

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

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的引用也不會變,簡直是性能優(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,會提高React Forget實(shí)現(xiàn)的難度

所以,useEvent并沒有正式進(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. 始終返回不同的引用。

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

畢竟,同樣遵循Hooks理念的Vue Composition API就沒有這方面問題。

參考資料

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

責(zé)任編輯:姜華 來源: 魔術(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-20 14:10:00

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

2014-07-02 10:03:42

App推廣渠道

2009-10-01 09:19:45

PHP框架ZendFramewoCake

2021-06-21 15:49:39

React動(dòng)效組件

2022-03-16 17:01:35

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

2024-03-19 00:00:00

ReactJavaScript開發(fā)

2016-08-04 14:59:56

分段路由器路由器

2015-08-06 17:15:28

2024-04-15 12:54:00

ReactVue列表邏輯

2023-01-30 09:01:34

DecoratorsJS語法

2009-04-20 23:29:12

Oracle收購Sun甲骨文

2022-06-06 09:28:36

ReactHook

2018-10-22 13:53:02

無人零售無人貨架智能

2022-10-29 08:55:19

頁面react

2023-05-15 08:30:35

YjsReact

2009-04-09 08:59:33

Windows 7微軟操作系統(tǒng)

2022-01-04 12:41:37

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

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