和传统的 HTML 页面是由元素组成的一样,在 React 中,构建页面的基础单元是 React 组件。React 组件就好比混入了 JavaScript 表达能力的 HTML 元素。实际上写 React 代码主要是在构建组件,而构建组件就像编写 HTML 文档时使用元素一样。
React + JSX 是强大而富有表现力的工具,允许我们使用类似 HTML 的语法创建自定义元素。比起传统的 HTML,这些自定义元素还能够控制生命周期中的行为。
相比较于继承,React 更-偏向于复合,也就是通过结合小巧的、简单的组件和数据对象,构造大而复杂的组件。正如在构建网页时不会扩展 HTML DOM 节点那样,React 组件是不可以扩展的,而是通过组件之间的组合来构建复杂的应用的。
一个渲染选择题的组件要满足以下几个条件:
HTML 提供了一些基本的元素 —- 单选类型的输入框和表单组,可以在这里使用。组件的层级从上往下看是这样的:
MultipleChoice -> RadioInput -> Input (type="redio");
我们从下网上组装这个组件。先建立一个脚手架,其中包含所需的渲染方法和基本的标记,用以描述想输出的界面。组合模式开始显现,组件变成了特定类型的输入框:
var AnswerRadioInput = React.createClass({ render: function() { return ( <div className="radio"> <label> <input type="radio" /> </label> </div> ); } });
现在 input
还没有内容是动态的,所以下一步需要定义父元素必须传给单选框的那些属性:
name
是什么?(必填) id
。 有了上述的描述后我们就可以开始自定义 input
的属性类型了。我们把这些添加到类的 PropTypes
对象中:
var AnswerRadioInput = React.createClass({ proTypes: { id: React.PropTypes.string, naame: React.PropTypes.string.isRequired, label: React.PropTypes.string.isRequired, value: React.PropTypes.string.isRequired, checked: React.PropTypes.bool }, ... });
对于每个非必须的属性我们都应该为其定义一个默认值。把它们添加到 getDefaultProps()
方法中。在每个新的实例中,如果伏组件没有传递数据给它们,这些定义的默认值就会被使用。
由于这个发那个发只会在类上 调用一次 ,而不是在每个实例上都调用,因此不能在这里提供 id
—- 每个实例应该保持 id
的唯一性。
前面的笔记中有提到过对于变化的数据,应该用 state
来存放,所以我们应该把 id
的值放在 state
中。
var AnswerRadioInput = React.createClass({ proTypes: {...}, getDefaultProps: function() { return { id: null, checked: false }; }, ... });
我们的组件需要记录随时间而变化的数据。尤其是对于每个实例来说都要求是唯一的 id
,以及用户可以随时更新的 checked
值:
var AnswerRadioInput = React.createClass({ proTypes: {...}, getDefaultProps: function() {...}, getInitialState: function() { var id = this.props.id ? this.props.id : uniqueId('radio-'); return { checked: !!this.props.checked, id: id, name: id }; }, ... });
现在你可更新渲染标记,获取新动态的状态和属性了:
var AnswerRadioInput = React.createClass({ proTypes: {...}, getDefaultProps: function() {...}, getInitialState: function() {...}, render: function() { return ( <div className="radio"> <label htmlFor={this.props.id}> <input type="radio" name={this.props.name} id={this.state.id} checked={this.state.checked} /> {this.props.label} </label> </div> ); }, ... });
目前这个组件已经足够完善了,可以把它应用到一个父组件中了。接下来我们来构建下一层 —- AnswerMultipleChoiceQuestion
。这一层的组要作用是渲染一列选项让用户从中选择。同样按照上面的方法,可以先创建这个组件基本的 HTML 和默认属性:
// Page 48.
为了生成一列单选框子组件,我们需要对选项列表进行映射,把每一个项转化为一个组件:
// Page 48.
现在 React 的可组合性显得更清晰了。从一个通用的输入框开始,将其定制为一个单选框,最终将其封装进一个选择题组件 —- 一个高度定制具备特定功能的表单控件。现在渲染一列选项就简单了:
<AnswerMultipleChoiceQuedtion choices={arrayOfChoices} ... />
看到这里你可能会觉得有点怪怪的 —- 单选框怎么把自身的变化通知给父组件呢?
此时我们已经可以把一个表单组件渲染到页面上了,不过此时子组件还没有能力与父组件进行通信。
要如何使得父子组件可以通信呢?最简单的方法就是使用 props
属性。父组件可以通过属性传入一个回调函数,子组件在需要时进行调用。
首先需要定义一个 AnswerMultipleChoiceQuestion
在其子组件变更后要做什么。添加一个 handleChanged()
方法然后把它传递给所有的 AnswerRadioInput
组件:
var AnswerMultipleChoiceQuestion = React.createClass({ ..., handleChanged: function() { this.setState({ value: value }); this.props.onCompleted(value); }, renderChoices: function() { return this.props.choices.map(function() { return AnswerRadioInput({ ..., onChanged: this.handleChanged }); }).bind(this); }, ... });
现在需要让每个单选框监听用户的更改,然后把数值向上传递给父组件。这需要将一个事件处理函数关联到输入框的 onChange
事件上:
// Page 51.
所以,编写 React 实际上就是在编写 React 的组件,而编写 React 组件又实际上是在编写 HTML。然后 React 父子组件可以通过 props
属性进行简单通信。