React的Nested Component模式
JSX dot notation一个偶然的机会,发现React的JSX语法里,Component Type是可以写成这样的: <this.FlatButton /> React/JSX的Component Type是支持dot notation的,主要是为了方便把一组Component装在一个Object容器里,这样在export/import的时候很方便;如果一个Component是匿名的或者名字是小写字母开头的,JSX并不接受,但可以用一个大写字母开始的变量名来转换一下。 在React的官方文档中给出来的使用dot notation的例子是: <MyComponents.DatePicker color="blue" /> 它给人一个错觉,似乎容器的名字也必须是大写字母开始的,但简单试一下就知道并非如此,dot notation前面的容器名字没有任何限制。例如 import { FlatButton } from 'material-ui' const wrap = { MyButton: FlatButton } // in render <wrap.MyButton label='hello' /> 是完全可用的。Anyway,我们澄清了一个细节,JSX支持dot notation,这一点没问题,dot前面的容器对象名称无限制,所以 Nested Component那么,为什么要这样用呢? 我举一个例子。比如在绑定行为时我们经常有这样的写法: class Foo extends React.Component { handle() { // ... } render () { <FlatButton onTouchTap={this.handle.bind(this)} /> } } 如果不想总是写 class Foo extends React.Component { constructor(props) { super(props) this.handle = () => { // ... } } render () { <FlatButton onTouchTap={this.handle} /> } } 把 上面的例子里没有参数传递,如果有参数传递,后者的写法很少会犯错误,但前者有时候忘了bind,或者搞错了参数形式都容易出问题。 Function Component同样的,如果我们把component用类似的方式定义在constructor内,如果这是一个function component,它可以直接访问容器内的 如果子组件对应多个数据对象实例,那么只要把这个数据对象本身作为props传递给子组件即可,例如: class Foo extends React.Component { constructor(props) { super() this.state = { selected: [] } this.deleteItem = item => { //... } this.Bar = props => { let item = props.item return ( <div> {item.name} <FlatButton label='delete' onTouchTap={() => this.deleteItem(item)} /> </div> ) } } render() { <div> { this.props.items.map(item => <this.Bar item={item} />) } </div> } } 这样写的 但好处不限于此。 在实际的场景中,常常出现因为
但是如果写成上述的形式,抽取共用的部分仍然可以写成 Class Component当然上面写的都是Function Component,可以定义为arrow function,写在父组件的构造函数里,共享父组件的 同样可以。 虽然我们可能很少在实践中写出匿名class,但是在JavaScript里它是合法的。上面的 class Foo extends React.Component { constructor(props) { super() const that = this this.state = { selected: [] } this.deleteItem = item => { //... } this.Bar = class extends React.Component { constructor(props) { super(props) this.state = { open: false } } render() { let item = this.props.item return ( <div> {item.name} <FlatButton label='delete' onTouchTap={() => that.deleteItem(item)} /> </div> ) } } } render() { <div> { this.props.items.map(item => <this.Bar item={item} />) } </div> } } 写成这样之后,在Bar里面的 这样无论是Function Component还是Class Component都可以nest在父组件中,不仅可以直接访问父组件的全部上下文,更可以方便共享表示和行为,直接在子组件的方法内调用 And More还不仅如此; 父组件作为上下文还有其他功效,例如: class Foo extends React.Component { constructor(props) { super() const that = this this.colors = { primary: () => '#FF89E0',secondary: () => '#DD7633',// ... } this.dims = { tableHeaderHeight: () => 64,tabelDataHeight: () => 48,// ... } this.styles = { mainText: () => ({ fontSize: 14,fontWeight: this.state.editing ? 'normal' : 'bold',}) // ... } } } 你可以看出父组件完全可以自己作为一个上下文的小世界,定义统一的color,dimension和style体系;他们都在父组件的构造函数内,因此可以在此访问所有状态,如果需要在这个组件内做动态,这非常方便。 Summary如果你理解JavaScript的class和闭包是高度相似的(把function scope当成对象来理解),你就理解这个Pattern的要义:把React.Component从class对象翻成了类似闭包的基于lexical scope的context工作的方式。 既然React.Component不能基于class继承实现重用,那么为什么不这么做让书写代码变得容易呢?在这个context内,你连额外的状态管理器(例如redux)都不需要,因为一切都是全局的,在任何地方都可以调用父组件的 我在过去的两天里把一个大约2000-3000行代码的单页面写成了这种形式,目前感觉非常好,不再有奇怪的不容易觉察的行为binding,也扔掉了所有的 当然这种做法反模式的地方是,这样写在容器内的组件在外部无法重用了,是的,如果需要外部重用我们仍然要回到写独立的React组件的模式,但是对于实际应用中,很多复杂组件都有自己的独特性,而容器拆解不可避免,所以至少在不太需要外部重用的地方,这种Nested Component Pattern,是一种不但可行,而且非常简洁易用的方式。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |