原文:https://www.merrickchristense…
“策略同机制拆散,接口同引擎拆散。”— Eric S. Raymond
Headless ui组件是一种新的ui组件开发模式,组件自身不提供ui上的实现,从而让使用者可能自在定制ui款式。“且慢,无ui的ui组件,你晓得本人在说什么吗?”
没错,尽管反直觉,但这正是咱们所提倡的。
Coin Flip Component
假如你要实现一个抛硬币的性能,需要是这样的:实现一个相似硬币翻转的成果,翻转完结后,硬币侧面朝上和背面朝上的概率是对半开。你和产品说,这需要有点简单,给我半年的工夫调研一下,而后你开始写demo
const CoinFlip = () =>
Math.random() < 0.5 ? <div>Heads</div> : <div>Tails</div>; ```
太简略了,而后你拉了个会,拿着ppt就下来了。产品说不错,性能是有了,你把款式优化一下。对你来说问题不大。
const CoinFlip = () =>
Math.random() < 0.5 ? (
<div>
<img src="/heads.svg" alt="Heads" />
</div>
) : (
<div>
<img src="/tails.svg" alt="Tails" />
</div>
);
没多久,他们心愿能在营销页上线你这个性能。他们打算投放到博客推文里,心愿你的组件可能对SEO敌对。于是你撸起袖子持续开干。
const CoinFlip = (
// 设定默认值为false,免得对之前的利用造成毁坏。
// current usage.
{ showLabels = false }
) =>
Math.random() < 0.5 ? (
<div>
<img src="/heads.svg" alt="Heads" />
{/* 减少label标签,用于营销页 */}
{showLabels && <span>Heads</span>}
</div>
) : (
<div>
<img src="/tails.svg" alt="Tails" />
{/* 减少label标签,用于营销页 */}
{showLabels && <span>Tails</span>}
</div>
);
而后又来了一个需要,加一个重来的按钮,并且这个按钮只在应用程序当中增加。组件开始变得俊俏了起来
const flip = () => ({
flipResults: Math.random(),
});
class CoinFlip extends React.Component {
static defaultProps = {
showLabels: false,
// We don't repurpose `showLabels`, we aren't animals, after all.
showButton: false,
};
state = flip();
handleClick = () => {
this.setState(flip);
};
render() {
return (
// Use fragments so people take me seriously.
<>
{this.state.showButton && (
<button onClick={this.handleClick}>Reflip</button>
)}
{this.state.flipResults < 0.5 ? (
<div>
<img src="/heads.svg" alt="Heads" />
{showLabels && <span>Heads</span>}
</div>
) : (
<div>
<img src="/tails.svg" alt="Tails" />
{showLabels && <span>Tails</span>}
</div>
)}
</>
);
}
}
而后有一天你共事找到你:“好兄弟,你的掷硬币性能碉堡了。咱们有个新需要叫投骰子,能够复用你的代码么?“你拆分了一下新需要:
1. 须要重来按钮
1. 须要同时用于应用程序和营销页
1. 和你的组件有着齐全不一样的ui
1. 有着不一样的随机概率
当初你面临两种抉择,要不就是回绝你的共事,要不就是革新你的组件,把投骰子性能赛到掷硬币组件当中,看着这个组件变得臃肿难以保护。
# 应用Headless组件
Headless ui组件将本身的ui和行为分离出来。当一个组件的行为足够简单,并且逻辑与视觉体现能够解耦时,这种模式十分无效。CoinFlip组件实现Headless的形式能够是让具体的ui实现作为一个子组件或者是renderProp传入,就像上面这样:
const flip = () => ({
flipResults: Math.random(),
});
class CoinFlip extends React.Component {
state = flip();
handleClick = () => {
this.setState(flip);
};
render() {
return this.props.children({
rerun: this.handleClick,
isHeads: this.state.flipResults < 0.5,
});
}
}
下面这个组件是一个headless ui组件,因为这个组件不渲染任何内容。它实现了逻辑状态的晋升,冀望消费者去做理论的渲染工作。所以回到咱们的利用,代码可能长这样:
<CoinFlip>
{({ rerun, isHeads }) => (
<>
<button onClick={rerun}>Reflip</button>
{isHeads ? (
<div>
<img src="/heads.svg" alt="Heads" />
</div>
) : (
<div>
<img src="/tails.svg" alt="Tails" />
</div>
)}
</>
)}
</CoinFlip>
而后是咱们的营销页,它可能长这样:
<CoinFlip>
{({ isHeads }) => (
<>
{isHeads ? (
<div>
<img src="/heads.svg" alt="Heads" />
<span>Heads</span>
</div>
) : (
<div>
<img src="/tails.svg" alt="Tails" />
<span>Tails</span>
</div>
)}
</>
)}
</CoinFlip>
完满,咱们对逻辑和状态进行了很好的形象,剥离了ui,这样咱们就能够随便的定制咱们的ui了。我晓得你可能在想什么...
> 你是不是傻,不就是一个renderProp么,有必要绕来绕去么?
这个例子当中,恰好咱们是利用renderProp去实现它。在react当中,咱们当然也能够用HOC来实现。略微扩散一下思维,咱们甚至能够将其实现为MVC当中的View和Controller,或者MVVM中的ViewModel和View。(注:将组件外部的逻辑状态封装,具体的渲染和事件绑定交由渲染框架,独自的开发适配层,组件甚至能够做到跨平台)这里的核心思想是拆散组件的机制和体现。
# 回到投骰子组件
这种拆散的益处是,咱们很容易扩大咱们的headless组件,以反对共事的投骰子性能:
const run = () => ({
random: Math.random(),
});
class Probability extends React.Component {
state = run();
handleClick = () => {
this.setState(run);
};
render() {
return this.props.children({
rerun: this.handleClick,
// 设置不同的threshold,失去不同的概率
result: this.state.random < this.props.threshold,
});
}
}
因为是headless组件,咱们只须要更新CoinFlip组件的代码,而不须要去批改上级消费者的代码。
const CoinFlip = ({ children }) => (
<Probability threshold={0.5}>
{({ rerun, result }) =>
children({
isHeads: result,
rerun,
})
}
</Probability>
);
同样的,共事也能够通过复用Probability组件来实现他们的逻辑
const RollDice = ({ children }) => (
// 六面骰子
<Probability threshold={1 / 6}>
{({ rerun, result }) => (
<div>
{/* 这里能够实现一些自定义事件 */}
<span onMouseOver={rerun}>Roll the dice!</span>
{/* 齐全不同的ui实现 */}
{result ? (
<div>Big winner!</div>
) : (
<div>You win some, you lose most.</div>
)}
</div>
)}
</Probability>
);
优雅,十分优雅。
# 拆散准则-Unix设计哲学
这是一个广受业界认可的共识,并且经久不衰。Unix设计哲学根底第四条:
>"策略同机制拆散,接口同引擎拆散。"— Eric S. Raymond
我想援用这一部分,并且用界面代替策略这个词。
> 因为界面和机制是依照不同的时间尺度变动的,界面的变动要远远快于机制。GUI工具包的观感时尚来去匆匆,而光栅操作和组合的确永恒的。
所以,把界面同机制揉成一团有两个负面影响:一来会使界面变得死板,难以适应用户需要的扭转,二来也意味着任何界面的扭转都极有可能波动机制。
相同,将两者剥离,就有可能在摸索新界面的时候不足以突破机制。另外,咱们也能够更容易为机制写出较好的测试(因为界面太长寿,不值得花太多精力在这下面)。
我喜爱这里的粗浅见解,这也给咱们带来思考,哪些状况下headless ui设计模式是十分有价值的。
1. 这个组件的寿命有多久?抛开界面体现,背地的机制是否值得咱们刻意保留?这个机制咱们是否会用在另外一个外观和格调齐全不同的我的项目当中。
2. 咱们的界面外观多久更新一次,同样的性能,咱们会有多少不同的外观界面?
将机制和政策拆散,是有老本的。咱们须要均衡好拆散带来的收益和老本。我认为这是过来许多MV*模式容易犯错的中央,就是死守这个准则,所有都以这种形式去拆散。回到事实,很多机制和政策都是深度耦合的,拆散的益处可能不足以笼罩所带来的老本。
发表回复