JSX dot notation
@H_
502_2@一个偶然的机会,发现React的JSX语法里,Component Type是可以写成这样的:
<this.FlatButton />
@H_
502_2@React/JSX的Component Type是
支持dot notation的,主要是为了方便把一组Component装在一个Object容器里,这样在export/import的时候很方便;如果一个Component是匿名的或者名字是小写字母开头的,JSX并不接受,但可以用一个大写字母开始的变量名来转换一下。
@H_
502_2@在React的官方文档中给出来的使用dot notation的例子是:
<MyComponents.DatePicker color="blue" />
@H_
502_2@它给人一个错觉,似乎容器的名字也必须是大写字母开始的,但简单试一下就知道并非如此,dot notation前面的容器名字没有任何限制。例如
import { FlatButton } from 'material-ui'
const wrap = {
MyButton: FlatButton
}
// in render
<wrap.MyButton label='hello' />
@H_
502_2@是完全可用的。Anyway,我们澄清了一个细节,JSX
支持dot notation,这一点没问题,dot前面的容器对象
名称无限制,所以
<this.FlatButton />
可以工作。
Nested Component
@H_
502_2@那么,为什么要这样用呢?
@H_
502_2@我举一个例子。比如在绑定行为时我们经常有这样的写法:
class Foo extends React.Component {
handle() {
// ...
}
render () {
<FlatButton onTouchTap={this.handle.bind(this)} />
}
}
@H_
502_2@如果不想总是写
bind(this)
,我们可以换一种方式写:
class Foo extends React.Component {
constructor(props) {
super(props)
this.handle = () => {
// ...
}
}
render () {
<FlatButton onTouchTap={this.handle} />
}
}
@H_
502_2@把
handle
从类
方法中搬到构造
函数里去定义成为arrow function,虽然arrow function不能bind this,但是在
函数内写this仍然是有效的,因为constructor里有this。
@H_
502_2@上面的例子里没有参数传递,如果有参数传递,后者的写法很少会犯
错误,但前者有时候忘了bind,或者搞错了参数形式都容易出问题。
Function Component
@H_
502_2@同样的,如果我们把component用类似的方式定义在constructor内,如果这是一个function component,它可以直接访问容器内的
this
,当然也就能直接访问容器内的
this.state
和
this.props
,这样直接的好处就是可以不用props翻译父组件的state或者props传递给子组件。
@H_
502_2@如果子组件对应多个数据对象实例,那么只要把这个数据对象本身作为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>
}
}
@H_
502_2@这样写的
Bar
Component,直接在父组件的构造
函数内,它当然可以随意访问父容器的
state
和
props
;
@H_
502_2@但好处不限于此。
@H_
502_2@在实际的场景中,常常出现因为
item
的数据对象是多态的,我们可能需要定义很多种
Bar
来实现不同的
显示和行为,在这种情况下,各种
Bar
的实现里,不论是行为还是表示,都有很多共用的地方。但是React的Component并不能使用继承的方式来实现共性;所以实际的情况是:
- @H_502_2@对于表示,如果
MyFirstBar
和MySecondBar
之间需要共用,那么仍然需要抽取Component。
- @H_502_2@对于行为,写在父组件里,向子组件binding。
@H_
502_2@但是如果写成上述的形式,抽取共用的部分仍然可以写成
this.BarCommonPart
这样的形式,同样的,无须传递
props
,抽取共同行为的部分就更加简单了,在子组件之内直接
调用父组件
方法即可,不需要用
onSomethingHappened
之类的
props
传递。
Class Component
@H_
502_2@当然上面写的都是Function Component,可以定义为arrow function,写在父组件的构造
函数里,共享父组件的
this
,那么如果子组件需要有态呢?需要是Class Component呢?
@H_
502_2@同样可以。
@H_
502_2@虽然我们可能很少在实践中写出匿名class,但是在JavaScript里它是合法的。上面的
Bar
如果是Class Component,结果是这样:
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>
}
}
@H_
502_2@写成这样之后,在Bar里面的
this
不再指向父组件了,而是指向了子组件自己;但是我们可以在父组件容器里定义一个
that
,作为闭包或者叫词法域(lexical scope)变量,在整个
Bar
的内部这个
that
都是可用的。
@H_
502_2@这样无论是Function Component还是Class Component都可以nest在父组件中,不仅可以直接访问父组件的全部上下文,更可以方便共享表示和行为,直接在子组件的
方法内
调用this.setState()
或者
that.setState()
更新父组件的行为也完全不是问题。
And More
@H_
502_2@还不仅如此;
@H_
502_2@父组件作为上下文还有其他功效,例如:
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',})
// ...
}
}
}
@H_
502_2@你可以看出父组件完全可以自己作为一个上下文的小世界,定义统一的color,dimension和style体系;他们都在父组件的构造
函数内,因此可以在此访问所有状态,如果需要在这个组件内做动态,这非常方便。
Summary
@H_
502_2@如果你理解JavaScript的class和闭包是高度相似的(把function scope当成对象来理解),你就理解这个Pattern的要义:把React.Component从class对象翻成了类似闭包的基于lexical scope的context工作的方式。
@H_
502_2@既然React.Component不能基于class继承实现重用,那么为什么不这么做让书写
代码变得容易呢?在这个context内,你连额外的状态管理器(例如redux)都不需要,因为一切都是全局的,在任何地方都可以
调用父组件的
setState
方法,而结果就是所有子组件都可以体现变化。
@H_
502_2@我在过去的两天里把一个大约2000-3000行
代码的单
页面写成了这种形式,目前感觉非常好,不再有奇怪的不容易觉察的行为binding,也扔掉了所有的
props/state
传递,也不需要什么额外的东西来管理状态。
@H_
502_2@当然这种做法反模式的地方是,这样写在容器内的组件在外部无法重用了,是的,如果需要外部重用我们仍然要回到写独立的React组件的模式,但是对于实际应用中,很多复杂组件都有自己的独特性,而容器拆解不可避免,所以至少在不太需要外部重用的地方,这种Nested Component Pattern,是一种不但可行,而且非常简洁易用的方式。