React 當中如何讓在用 array 動態渲染 element 中綁定的 event Handler 可以取得 data

這是一個相當常見情境,我們在做一個 List Component 時用 props 或者是 state 裡頭的 array 來 render ListItem,並且綁定事件到 ListItem 上,event Handler 要能 access 原本 array 上的 data。本文會介紹我用過的幾種方法來達到這樣的目的。

一、封裝成 Componet <ListItem />

這是一個比較常見的作法創建一個 Component ListItem,event Handler 是寫在 ListItem 裡面,data 透過 props 傳進 ListItem,優點是簡單直覺,而且因為不會製造出新的 function instance,可以相當容易的避免重複 render ,缺點是要寫比較多行比較繁瑣,當然你也可以用 stateless functional component 來寫 ListItem,這時 event handler 改用 props 傳入 ListItem。

二、不封裝成 Component 直接 render div

有的時候又懶得再寫一個 Component,直接 inline 嵌入 div 在 map 裡面,這時候我們可以用一個 higher order function 來作為 function factory,利用 closure 的特性來 cache 要傳入的 data,然而這樣的作法會造成每一次 render 的時候 higher order function 都會回傳一個新的 function instance 給 props,無法避免重新 render,所以必須要一些方式來確保 higher order function 在相同的 input 底下會回傳相同的 instance,有一些做 cache 的 library 如 lodash的 memoize 可以達成這樣的效果。

三、React 也可以玩 Event delegation

React 本身在 virtual DOM 上 follow spec 重新 implement 了 DOM 的事件系統,所以也同時模擬一般 DOM 上的 event capture 跟 event bubble 的行為,所以我們可以把事件 bind 在 List,把 index 藏在 ,event Handler 會在 event bubble phase 的時候觸發,利用 event.target 會指向事件觸發的 DOM element 的特性,就可以取得 element 上 data attribute 的資料,這邊要小心的地方就是事件觸發不見得一定會在 ListItem 上,也有可能因為 layout 的關係使用者點到的是 List 的 div,這邊可以用 event.target.matches(selector) 來過濾

然而 event.target 只能適用在單純 ListItem 只有一個 DOM element 的情形下,如果遇到複雜一點 nested 的 ListItem 帶有 children,svg,Component 你就很難控制觸發事件的 event.target 有沒有都有帶上 data attribute 的資料。

這時我們可以改把事件 bind 在 ListItem 上,並利用 event.currentTarget 會指向事件綁定的 DOM element 的特性取得 data attribute,這樣就不用擔心 event.target 會指向哪裡的問題了。

但如果真的很複雜我覺得還是用作法一把 ListItem 抽出來比較好。

小結

對於新手跟 general case 我比較推薦作法一,對 higher order function 有愛的 closure 熟悉的玩家可以嘗試作法二,想要練練 event delegation 可以嘗試一下作法三。但對於作法二跟作法三寫法對於複雜的 ListItem 我並不會推薦,畢竟像這種 inline 偷懶的寫法是沒有辦法用 shouldComponentUpdate 優化的。

Like what you read? Give 陳冠霖 a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.