书籍完整目录html
在 React 中组件是第一元素,是 React 的基础,一个 React 应用就是基于 React 组件的组合而成。
前面的 JSX 练习事后,你们应该对 React 组件不陌生了,在这一节咱们将温习以及深刻学习 React 组件。node
建立一个 React 组件的方法为,调用 React.createClass 方法,传入的参数为一个对象,对象必须定义一个 render 方法,render 方法返回值为组件的渲染结构,也能够理解为一个组件实例(React.createElement 工厂方法的返回值),返回值有且只能为一个组件实例,或者返回 null/false,当返回值为 null/false 的时候,React 内部经过 <noscript/> 标签替换。react
eg:git
var MyComponent = React.createClass({ render: function() { return <p>....</p>; } });
能够看出 React.createClass 生成的组件类为一个 Javascript 对象。 当咱们想设置命名空间组件时,能够在组件下面添加子组件:github
eg:segmentfault
MyComponent.SubComponent = React.createClass({...}); MyComponent.SubComponent.Sub = React.createClass({....});
在组件较多的状况下,能够借助命名空间优化组件维护结构以及解决组件名称冲突问题。数组
除了能够经过 React.createClass 来建立组件之外,组件也能够经过一个普通的函数定义,函数的返回值为组件渲染的结果。缓存
eg:babel
function StatelessComponent(props) { return <div> Hello {props.name} </div> }
无状态组件可以优化性能,由于其内部不会维护状态,React 内部不会有一个对应的组件实例,而且也没有生命周期 hook。app
当建立好了组件事后,为了将组件渲染到 DOM 中显示出来,须要两个步骤
在 HTML 中定义一个元素,设置 id 属性
JSX 中调用 ReactDOM.render 方法, 第一个参数为 组件,第二个为刚才定义的 DOM 元素
eg:
<!-- 定义的 DOM 元素 --> <div id="example"></div> <script type="text/babel"> // 自定义组件 var MyComponent = React.createClass({ render: function() { return <p>....</p>; } }); // 调用 render 方法 ReactDOM.render( <MyComponent/>, document.getElementById('example') ); </script>
对于组件的渲染,可能涉及到的一些问题:
Q1: 只能 render 到一个元素吗?
Q2: 在程序运行时可以动态的调用 render 吗?
Q3: 修改了数据事后,还须要从新调用 render 方法吗?
这里要先提一下 React 的设计初衷,React 在开发时候的目标就是简单精巧,能够和其余框架结合起来使用。简单而言咱们能够当 React 是一个渲染数据对象到 DOM 中的 Javascript 函数工具类,工具类固然能够屡次使用。
那么对于上面的问题:
A1: 不是,React 能够渲染到多个元素,任意位置的元素。
A2: 能够,好比以一个弹出层组件为例,咱们但愿建立一个 append 到 body 最后的组件来实现全屏遮罩, 那么咱们能够动态的建立一个 div append 到 body 最后,而后将弹出层 render 到那个 div 内。
A3: 假设每次数据改变都从新调用 render 方法,那么每次 render 带来的结果是从新建立一个顶级组件实例,以及子组件实例。 若是只调用 render 一次,将数据的变动放在组件内部,那么就不会重复建立顶级组件。
React 中每一个组件能够存储本身的当前状态, 以一个 switch 开关组件为例,开关的状态能够存储在组件内部。
React 的渲染结果是由组件属性和状态共同决定的,状态和属性的区别是,状态维护在组件内部,属性是由外部控制,咱们先介绍组件状态相关细节:
控制状态的 API 为:
this.state:组件的当前状态
getInitialState:获取组件的初始状态,在组件加载的时候会被调用一次,返回值赋予 this.state 做为初始值
this.setState:
组件状态改变时,能够经过 this.setState 修改状态
setState 方法支持按需修改,如 state 有两个字段,仅当 setState 传入的对象包含字段 key 才会修改属性
每次调用 setState 会致使重渲染调用 render 方法
直接修改 state 不会重渲染组件
eg:
var Switch = React.createClass({ // 定义组件的初始状态,初始为关 getInitialState: function() { return { open: false } }, // 经过 this.state 获取当前状态 render: function() { console.log('render switch component'); var open = this.state.open; return <label className="switch"> <input type="checkbox" checked={open}/> </label> }, // 经过 setState 修改组件状态 // setState 事后会 React 会调用 render 方法重渲染 toggleSwitch: function() { var open = this.state.open; this.setState({ open: !open }); } })
前面已经提到过 React 组件能够传递属性给组件,传递方法和 HTML 中无异, 能够经过 this.props 获取组件属性
属性相关的 API 为:
this.props: 获取属性值
getDefaultProps: 获取默认属性对象,会被调用一次,当组件类建立的时候就会被调用,返回值会被缓存起来,当组件被实例化事后若是传入的属性没有值,会返回默认属性值
this.props.children:子节点属性
propTypes: 属性类型检查
以一个代办事项的列表项组件为例:
// props.name 表示代办事项的名称 var TodoItem = React.createClass({ render: function() { var props = this.props; return <div className="todo-item"> <span className="todo-item__name">{props.name}</span> </div> } }); ReactDOM.render( <TodoItem name="代办事项1"/>, document.getElementById('example'));
组件属性中会有一个特殊属性 children ,表示子组件, 仍是以上面一个组件为例子,咱们能够换一种方式定义 name:
var TodoItem = React.createClass({ render: function() { var props = this.props; return <div className="todo-item"> <span class="todo-item__name">{props.children}</span> </div> } }); ReactDOM.render( <TodoItem>代办事项1</TodoItem>, document.getElementById('example'));
须要注意的是,children 只能为一个元素,不能为多个组件
为了保证组件传递属性的正确性, 咱们能够经过定义 propsType 对象来实现对组件属性的严格校验:
var MyComponent = React.createClass({ propTypes: { optionalArray: React.PropTypes.array, optionalBool: React.PropTypes.bool, optionalFunc: React.PropTypes.func, optionalNumber: React.PropTypes.number, optionalObject: React.PropTypes.object, optionalString: React.PropTypes.string, // 任何能够被渲染的包括,数字,字符串,组件,或者数组 optionalNode: React.PropTypes.node, // React 元素 optionalElement: React.PropTypes.element, // 枚举 optionalEnum: React.PropTypes.oneOf(['News', 'Photos']), // 任意一种类型 optionalUnion: React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.number, React.PropTypes.instanceOf(Message) ]), // 具体类型的数组 optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number), // 具体类型的对象 optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number), // 符合定义的对象 optionalObjectWithShape: React.PropTypes.shape({ color: React.PropTypes.string, fontSize: React.PropTypes.number }), requiredFunc: React.PropTypes.func.isRequired, requiredAny: React.PropTypes.any.isRequired, // 自定义校验 customProp: function(props, propName, componentName) {} } });
咱们已经提到过 React 的单向数据流模式,数据的流动管道就是 props,流动的方向就是组件的层级自定向下的方向。因此一个组件是不能修改自身的属性的,组件的属性必定是经过父组件传递而来(或者默认属性)。
对于无状态组件,能够添加 .propTypes 和 .defaultProps 属性到函数上。
在 1.2 节的 JSX 实例子中,当咱们循环输出 todo 列表的时候,React 会提示对于循环输出的组件,须要有一个惟一的 key 属性。这个问题的缘由在于 React 的调和机制(Reconciliation)上。
在每次数据更新事后,React 会从新调用 render 渲染出新的组件结构,新的结构应用到 DOM 中的过程就叫作调和过程。
想想,假设咱们有一个输入组件,这个时候咱们正聚焦在输入框中,当修改值事后触发事件致使了数据改变,数据改变致使了重渲染, 这个时候输入框被替换成了新的 DOM。 这个过程对用户来讲应该是无感知的,因此那原来的聚焦状态应该被保存, 那怎么作到的呢? DOM 都被替换了,输入状态,选择状态为何还能保存。 咱们先不急着知道 How,目前只须要知道这就是调和过程,后面咱们会在 React 实现原理章节进行详细介绍。
除了保存状态之外,调和过程还作了不少 DOM 优化。 好比输出一个数组的时候,数据新增长或者减小了一下,或者数组项值改变了,实际上咱们没有必要删除原来的 DOM 结构,只须要修改 DOM 的值或者删除 DOM 就能实现重渲染。
这就是为何要有 key 属性,key 属性可以帮助定位 DOM 与数组元素的关系,在重渲染的时候可以实现渲染优化。
优化 JSX 语法练习的 TODOMVC 页面, 经过组件化的方式拆分页面!
组件以下:
App 组件:整个页面的最完成组件
Header 组件:头部输入组件
TodoList 组件:列表组件
TodoItem 组件: 列表项
Footer 组件:底部操做组件
循环输出组件的方式
方式一:先计算出组件
function render() { var todos = this.props.todos; var $todos = todos.map(function(todo) { return <Todo data={todo}/> }); return <div> {$todos} </div> }
方式二:{} 内直接计算
function render() { var todos = this.props.todos; return <div> {todos.map(function(todo) { return <Todo data={todo}/> })} </div> }