解鎖 React 組件化新姿勢 react-call-return
最早的時候,antd 的 Table 用起來是這樣的:
const columns = [ { dataIndex: name, title: Name }, { dataIndex: age, title: Age }, { dataIndex: address, title: Address }]<Table columns={columns} data={data} />
只要把列定義和數據傳給 Table 組件,它就會幫你把表格渲染出來。我們簡單實現一下上面的 Table 組件:
class Table extends React.Component { render() { const { columns, data } = this.props; return ( <table> <thead> <tr> {columns.map(col => <th key={col.dataIndex}>{col.title}</th> )} </tr> </thead> <tbody> {data.map(item => ( <tr key={item.id}> {columns.map(col => <td key={col.dataIndex}>{item[col.dataIndex]}</td> )} </tr> ))} </tbody> </table> ); }}
後來,有人就覺得這樣寫不夠 React style 啊,我想要用 JSX 來定義列啊。
好吧,那我們把上面的 Table 改造一下,允許用 JSX 的方式定義列。
const Column = () => {};class Table extends React.Component { static Column = Column; render() { const { children, columns, data } = this.props; this.columns = columns || React.Children.map(children, (child) => ({ dataIndex: child.props.dataIndex, title: child.props.title, })) return ( <table> <thead> <tr> {this.columns.map(col => <th key={col.dataIndex}>{col.title}</th> )} </tr> </thead> <tbody> {data.map(item => ( <tr key={item.id}> {this.columns.map(col => <td key={col.dataIndex}>{item[col.dataIndex]}</td> )} </tr> ))} </tbody> </table> ); }}
這裡我們定義了一個什麼都不幹的 Column 組件,然後 Table 組件通過遍歷自己的子元素來把列信息讀出來。這樣就實現 issue 里要求的功能,非常簡單。
可是,過了段時間,又有人不滿意了。
既然現在支持 JSX 的方式定義列了,那我就想封裝自己的 XxxColumn 組件啊。但是從我們上面的實現可以看到,Table 是直接從自己的子元素上讀取列信息的,現在如果 Column 組件被封裝起來,我們就讀不到 Column 上的信息了。
這個問題,一直沒有很好的解法。但是,就在昨天,隨著 React 16.1 的發布,React Team 還發布了兩個實驗性的包 react-reconciler 和 react-call-return。其中 react-reconciler 用來實現自定義的 renderer,此處暫且不表。而 `react-call-return` 這個初看名字完全不知道什麼東西的包,就可以解決上面的問題。
react-call-return 提供了兩個方法 unstable_createReturn 和 unstable_createCall。
我們先來用這兩個方法重新實現一下上面的 Table 組件,再來看我們的問題是怎麼被解決的。
const Column = (props) => unstable_createReturn(props);class Table extends React.Component { static Column = Column; render() { const { children, data } = this.props; return ( <table> <thead> <tr> {unstable_createCall(children, (props, columns) => ( columns.map(col => ( <th key={col.dataIndex}>{col.title}</th> )) ), this.props)} </tr> </thead> <tbody> {data.map(item => ( <tr key={item.id}> {unstable_createCall(children, (props, columns) => ( columns.map(col => ( <td key={col.dataIndex}>{item[col.dataIndex]}</td> )) ), this.props)} </tr> ))} </tbody> </table> ); }}
可以看到,Column 組件不再什麼都不做了,它用 unstable_createReturn 創建了一個 return,return 的內容,就是它自己的 props。然後我們在 Table 里調用 unstable_createCall,這個方法的第一個參數是 Table 的子元素,第二個參數是一個 handler 方法,,第三個參數是 Table 的 props。因為 Table 的子元素,也就是 Column 返回了一個return,所以當我們 call 這些 return 的時候,React 會把 Column return 的結果傳給我們的 handler 方法,大概就是這個過程:
handler(props, children.map((c => c.call())))
這樣我們就拿到了 Column 上的 props,即使 Column 被封裝起來了也不會有問題。
react-call-return 的出現,讓我們在組件設計上有了更多的可能性,JSX 不再需要映射 DOM,而是可以直接來表達數據。
以上所有演示代碼,可以在這裡找到 yesmeck/neotable
推薦閱讀:
※如何看待 BrendanEich、Vjeux 等人就 React Native 的討論?
※「小白DAY7」細談設計稿還原
※Web Components 在 GitHub 中的應用
※如此多的前端框架是如何起名字的?
※奇舞周刊第 233 期:從源碼看瀏覽器如何載入資源