在設計元件的時候,有時候因應專案的需求要不斷的增加新功能,隨著功能的增加,該如何一邊增加功能一邊增強元件的維護性呢?一起來看看吧!
設計元件
一開始開發專案的時候,我們可能會先設計一個元件:
1 2 3 4 5 6 7 8 9
| const Button = ({ children, onClick, disabled }) => { return ( <button className="btn" onClick={onClick} disabled={disabled} >{children}</button> ); }
|
但是後來又其他需求,所以又要多加一些新的功能在同個元件上:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const handleWarning = (onWarning, onClick, type, target) => { if(type === 1) { onWarning(target); } else { onClick(target); } }
const Button = ({ children, onClick, disabled, type, onWarning }) => { const style = text === 1 ? 'btn-primary' : 'btn'; return ( <button className={style} onClick={(target) => handleWarning(onWarning, onClick, type, target)} disabled={disabled} >{children}</button> ); }
|
經過一次擴充component的功能之後,你會發現程式開始變大了,可能會有以下的狀況需要考量:
- 日積月累的legacy code難以維護
- 每次載入這個元件的運算成本越來越大
如何解決
React官方鼓勵大家使用composition的方式來設計component,特別是在Functional Component的設計模式下,更容易達成。
如果是以上面的例子來說,我們可以怎麼做呢?
初始元件基本上不動,不過可以調整一下樣式的擴充性:
1 2 3 4 5 6 7 8 9
| const Button = ({ children, onClick, disabled }) => { return ( <button className={`btn ${type ? `btn-${type}` : ''}`} onClick={onClick} disabled={disabled} >{children}</button> ); }
|
擴充元件,可以透過組合的方式來達成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const handleWarning = (onWarning, onClick, type, target) => { if(type === 1) { onWarning(target); } else { onClick(target); } }
const PrimaryButton = ({ text, onClick, disabled, type, onWarning, children }) => { return ( <Button type="primary" onClick={(target) => handleWarning(onWarning, onClick, type, target)} disabled={disabled} > {children} </Button> ) }
|
如此一來,原始Button元件能保持原本乾淨的樣子,你也可以基於原始元件去擴充設計同結構的元件來使用。
如果你還難以想像好處的話,可以再設計一個擴充元件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| const handleDelete = async (onValidate, onDelete, onClick, target) => { const validateResult = await onValidate(target); if(validateResult && validateResult.payload && validateResult.payload.success) { const deleteResult = await onDelete(target); if(deleteResult && deleteResult.payload && deleteResult.payload.success) { console.log('delete success!'); } else { console.log('please try again'); } } else { console.log('please try again'); } }
const DeleteButton = ({ onClick, disabled, type, onValidate, onDelete, children }) => { return ( <Button type="danger" onClick={(target) => handleDelete(onValidate, onDelete, onClick, target)} disabled={disabled} > {children} </Button> ) }
|
你會看到像這種delete button的情境,他需要做的工作其實會稍微比其他現有的元件多一些工作。
但是這種程式如果堆積起來會是很可怕的事情,因此搭配Composition設計樣式可以解決這種需求。
Composition與Inheritance的差別
學過物件導向的人可能會覺得,這個概念有點像繼承,把父類別想像成是原始元件,擁有它的特性。
但是其實只是概念相似,做的事情還是不太一樣的。
繼承會無條件繼承下來
Composition可以挑選你要用的東西使用,並不會有全部繼承的問題。
Composition的靈活度比Inheritance高
React官方有一篇文章曾經討論過,到底需不需要用到Inheritance。
結果是,他們認為Composition就能解決大部分的事情,沒必要特地設計成Inheritance。
Composition你也可以想像成是lego的組合方式,他可以組合成小功能,當然也能組合成很大的功能。
專業一點解釋Composition與Inheritance的差別
Composition是has-a的概念
以上面的例子來說,DeleteButton具有Button的特性。
因此我們可以說,DeleteButton has a Button.
Inheritance是is-a的概念
以物件導向的觀念來解釋的話,java.util.ArrayList繼承java.util.List。
那麼你可以說,ArrayList is a List。