文字是 ADVANCED-REACT-PATTERNS
第二篇。
Static 妙用
function App() {
return (
<Toggle
onToggle={on => console.log(`toggle`, on)}
>
<Toggle.On>The button is on</Toggle.On>
<Toggle.Off>The button is off</Toggle.Off>
<Toggle.Button />
</Toggle>
)
}
複製程式碼
首先外層是一個 Toggle元件,包括著內部的子元件。因為外層元件包裹的命名比較清晰。
注意這中間有不太常見的元件使用方式。
<Toggle.On>
複製程式碼
Toggle元件是一個 component 類, On 是它的屬性,不過既然作為元件使用,說明也是一個 component 類。
繼續研究下 Toggle 元件的實現,去尋找答案。
class Toggle extends React.Component {
static On = ToggleOn
static Off = ToggleOff
static Button = ToggleButton
static defaultProps = {onToggle: () => {}}
state = {on: false}
toggle = () =>
this.setState(
({on}) => ({on: !on}),
() => this.props.onToggle(this.state.on),
)
render() {
const children = React.Children.map(
this.props.children,
child =>
React.cloneElement(child, {
on: this.state.on,
toggle: this.toggle,
}),
)
return <div>{children}</div>
}
}
複製程式碼
在實際 debug 的時候元件渲染民時最終還是被替換成 <ToggleOn>
以及 <ToggleOff>
。
static 是 ES6 中 Class 定義靜態方法的關鍵字。靜態方法的好處是在基於類建立新的例項時,新的例項是不會繼承靜態方法的。(簡單來說,不是在原型鏈上定義的,而是直接掛載在這個類的屬性中)
這麼一看 Static 裡面的靜態屬性引入了外部定義的類(如 ToggleOn)
function ToggleOn({on, children}) {
return on ? children : null
}
function ToggleOff({on, children}) {
return on ? null : children
}
function ToggleButton({on, toggle, ...props}) {
return (
<Switch on={on} onClick={toggle} {...props} />
)
}
複製程式碼
通過 Static 關聯元件,是有原因的。
使用 React.cloneElement 減少子元件相同props傳遞
在 <Toggle.On>
等類似的元件中,我們發現 render 中並沒有直接傳入 props ,而 ToggleOn 元件就是接受了props,第一個物件為 on, 第二個則是包裹的 children。
程式碼產生的實際效果應該是這樣的。
render() {
<Toggle.On on={this.state.on}>The button is on</Toggle.On>
}
複製程式碼
我們再把思路回溯到 Toggle 的 render 這一方法。
render() {
const children = React.Children.map(
this.props.children,
child =>
React.cloneElement(child, {
on: this.state.on,
toggle: this.toggle,
}),
)
return <div>{children}</div>
}
複製程式碼
React.cloneElement
React.cloneElement 接受一個元件,以及可選的props以及children。它的作用就是 clone 一份原先元件並將其替換成新元件。原先元件的 props 會被新傳入的 props 淺合併。而新的children 會替換成原來的 children, 不過元件原先的 key 和 ref 會得到保留。
React.cloneElement(
element,
[props],
[...children]
)
複製程式碼
它的實際作用類似於
<element.type {...element.props} {...props}>{children}</element.type>
複製程式碼
由於 this.props.chidlren
的資料型別是不確定的,可能是個 Object,也可能是 Array.官方文件推薦使用 React.Chidlren 為 children 提供特定的資料操作。
新手很有可能編寫的程式碼結構
如果是我自己寫的話,基本就是以下結構,並且我相信大部分人一上來也是先寫了這段程式碼,後面再開始優化。
class Toggle extends React.Component {
static defaultProps = {onToggle: () => {}}
state = {on: false}
toggle = () =>
this.setState(
({on}) => ({on: !on}),
() => this.props.onToggle(this.state.on),
)
render() {
const { on } = this.state
return (<div>
<ToggleOn on={on}>The button is on</ToggleOn>
<ToggleOff on={on}>The button is off</ToggleOff>
<ToggleButton toggle={this.props.onToggle} />
</div>
)
}
}
function App() {
return (
<Toggle onToggle={on => console.log(`toggle`, on)} />
)
}
複製程式碼
我們再對比下就會發現,通過 cloneElemnt 確實可以讓我們的結構更加清晰。 而且,還可以少一個包裹的 div 標籤。雖然在 React16.2中出現了 <><>
的Fragment 表示可以避免此類問題,但是假設如果還有類似的10個元件需要接受相同的 props,我們要寫10遍 on={on}
,我們應該減少重複勞動。
分析
如果有批量的子元件需要傳遞相同的props,那麼使用 cloneElemnt 再好不過了。但需要注意的是,這也會給子元件傳遞一些不必要的資料。比如在這裡的 demo 中,ToggleOn 和 ToggleOff 只需要接受 on/off 的props 即可,傳入的 toggle 方法是冗餘的。我們假設它又接受了與自身所需資料無關的 其他 props, 而該 props 又頻繁變更,那麼每一次都會得到這些 props 的元件都會 re-render 的可能。
參考資料
- https://reactjs.org/docs/react-api.html#cloneelement
- https://doc.react-china.org/docs/react-api.html
- https://mxstbr.blog/2017/02/react-children-deepdive/