偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

一個新的React概念:Effect Event

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

大家好,我卡頌。

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

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

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

被誤用的useEffect

本文一共會涉及三個概念:

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

首先來聊聊Event與Effect。useEffect容易被誤用也是因為這兩個概念很容易混淆。

Event的概念

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

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

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

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

Effect的概念

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

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

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

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

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

為什么容易誤用?

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

舉個例子,在項目的第一個版本中,我們在useEffect中有個初始化數(shù)據(jù)的邏輯:

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

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

  // ...
}

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

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

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

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

  // ...
}

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

這么做實在是方便,以至于很多同學認為這就是useEffect的用法。但其實這是典型的「useEffect誤用」。

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

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

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

  // ...
}

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

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

屆時,很難清楚fetchData方法會在什么情況下執(zhí)行,因為:

  1. useEffect的依賴項太多了
  2. 很難完全掌握每個依賴項變化的時機

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

useEffect的依賴問題

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

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

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

接下來你接到了新需求 —— 當連接成功后,彈出「全局提醒」:

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

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

但這段代碼有個嚴重問題 —— 任何導致theme變化的情況都會導致聊天室斷開/重新連接。畢竟,theme也是useEffect的依賴項。

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

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

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

我們可以使用useEffectEvent(這是個試驗性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(他是個Effect Event)中,useEffect雖然使用了theme的最新值,但并不需要將他作為依賴。

useEffectEvent源碼解析

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

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的最新值。

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

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

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

useEffectEvent與useEvent

仔細觀察下useEffectEvent的返回值,他包含了兩個限制:

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);
};

第一個限制比較明顯 —— 下面這行代碼限制useEffectEvent的返回值只能在useEffect回調中執(zhí)行(否則會報錯):

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

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

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

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

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

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

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

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個優(yōu)點:

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

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

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

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

所以,useEvent并沒有正式進入標準。相反,擁有更多限制的useEffectEvent反而進入了React文檔[1]。

總結

今天我們學到三個概念:

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

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

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

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

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

參考資料

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

責任編輯:姜華 來源: 魔術師卡頌
相關推薦

2022-06-05 21:27:40

Reacteffect

2011-12-14 15:53:51

云計算

2009-10-20 14:10:00

CCIE經(jīng)驗

2014-07-02 10:03:42

App推廣渠道

2010-04-01 14:05:41

云計算

2009-10-01 09:19:45

PHP框架ZendFramewoCake

2021-06-21 15:49:39

React動效組件

2024-03-19 00:00:00

ReactJavaScript開發(fā)

2022-03-16 17:01:35

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

2016-08-04 14:59:56

分段路由器路由器

2023-01-30 09:01:34

DecoratorsJS語法

2009-04-20 23:29:12

Oracle收購Sun甲骨文

2024-04-15 12:54:00

ReactVue列表邏輯

2015-08-06 17:15:28

2022-06-06 09:28:36

ReactHook

2023-05-15 08:30:35

YjsReact

2018-10-22 13:53:02

無人零售無人貨架智能

2022-10-29 08:55:19

頁面react

2009-04-09 08:59:33

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

2014-06-20 10:42:27

Windows 8.1
點贊
收藏

51CTO技術棧公眾號