编者按:除组件化、虚拟DOM在复用以及性能上带来的一般好处外,React思想使得开发者之间更好的分工与合作,在配合上非常顺畅,规范的接口以及极强的约束,使得整个代码结构清晰,不同开发者的代码高度一致。
本次课程示例代码请前往react-tutorial。 示例中采用ES6语法来写React组件代码,提前明确以下几点:
- 不支持 getInitialState 在设置组件初始的 state ,可在组件的 constructor 中通过 this.state 设置,也可直接作为 properties 定义
- propTypes、defaultProps 作为 properties 定义,也可在组件外部通过键值对方式设置。
- 不支持 mixins,可以使用高阶组件写法,或者 decorator。
1. 顶层API
最简单的React组件及其渲染
import React, { Component } from 'react'; /** * 最简单的一个组件 */ class SimpleComponent extends Component { render(){ return <div> here we go </div>; } } // 我们可以看看React给我们提供了哪些顶层组件 console.log( React ); export default SimpleComponent;
react.js
React.Children: Object React.Component: ReactComponent(props, context, updater) React.DOM: Object React.PropTypes: Object React.cloneElement: (element, props, children) React.createClass: (spec) React.createElement: (type, props, children) React.createFactory: (type) React.createMixin: (mixin) React.isValidElement: (object)
Component API
this.context: Object this.props: Object this.refs: Object this.state: Object this.setState: Object
react-dom.js
ReactDOM.findDOMNode: findDOMNode(componentOrElement) ReactDOM.render: () ReactDOM.unmountComponentAtNode: (container)
react-dom-server.js
ReactDOMServer.renderToString ReactDOMServer.renderToStaticMarkup
2. jsx语法
类似 xml 的语法,用来描述组件树
<div classname="x"> <a href="#">#</a> <component x="y">1</component> </div>
不用JSX,用React提供的API写的话
React.createElement('div',{ className:'x' },[ React.createElement('a',{href:'#'},'#'), React.createElement(Component,{x:'y'},1); ]);
2.1 注释、命名、根元素个数、JSX 嵌入变量
import React, { Component } from 'react'; // 1. 组件命名遵循驼峰命名,首字母大写 class ComponentDemo extends Component { render(){ { /* 2. 这是代码注释 也可以是多行 */ } const name = this.props.name; // 3. render 方法 return 回来的根元素只能是一个,超过会报错 // 4. { } 里面可以写JS代码 return ( <div> hello, {name ? name : "我是默认的"} </div> ); } } export default ComponentDemo;
- React只有一个限制, 组件只能渲染单个根节点。如果你想要返回多个节点,它们必须被包含在同一个节点里。
2.2 styles
import React, { Component } from 'react'; class StyleDemo extends Component { render(){ // 5. 在JS文件里面给组件定义样式 var MyComponentStyles = { color: 'blue', fontSize: '28px' }; return ( <div style={MyComponentStyles}> 可以直接这样写行内样式 </div> ) } } export default StyleDemo;
2.3 JSX SPREAD
可以用通过 {...obj} 来批量设置一个对象的键值对到组件的属性,注意顺序
import React, { Component } from 'react'; class SpreadDemo extends Component { componentWillMount(){ console.log(this.props); } render(){ return <h1> {this.props.name} is a {this.props.type} </h1>; } } export default SpreadDemo;
2.4 属性名不能和 js 关键字冲突
例如:className, readOnly, htmlfor
3. 数据流:state props propType
3.1 state && setState
用状态控制组件变化 可以把一个组件看做一个状态机, 每一次状态对应于组件的一个 ui
组件内部的状态,可以使用 state
import React, { Component } from 'react'; class StateDemo extends Component { state = { secondsElapsed: 0 } tick(){ this.setState({ secondsElapsed: this.state.secondsElapsed + 1 }); } componentDidMount(){ this.interval = setInterval( this.tick.bind(this), 1000 ); } componentWillUnmount(){ clearInterval(this.interval); } render(){ return ( <div>目前已经计时:{this.state.secondsElapsed}秒</div> ) } } export default StateDemo;
3.2 props
通过 this.props 可以获取传递给该组件的属性值,还可以通过定义 getDefaultProps 来指定默认属性值(这是ES5的写法,ES6定义组件的默认props可以直接写props)
下面几个是props的常用API:
- this.props.children
- this.props.map
- this.props.filter
props是调用组件的时候传递进去的数据,一般用于组件树数据传递
import React, { Component } from 'react'; class PropsDemo extends Component { props = { title: '这是默认的title属性值' } render(){ console.log(this.props); return <b>{this.props.title}</b> } } export default PropsDemo; // 组件调用方式 // <PropsDemo title="设置的标题" />
3.3 propTypes
通过指定 propTypes 可以校验props属性值的类型,校验可提升开发者体验,用于约定统一的接口规范。
import React, { Component, PropTypes } from 'react'; class PropTypesDemo extends Component { static propTypes = { title: PropTypes.string.isRequired } props = { title: '默认的title' } render(){ return <b>{this.props.title}</b> } } export default PropTypesDemo;
4. 调用API定义组件
用 React.createClass或者React.Component 定义组件时允许传入相应的配置及组件API的使用,包括组件生命周期提供的一系列钩子函数。
4.1 组件初始定义
- getDefaultProps 得到默认属性对象,这个在ES6的时候不需要这样定义
- propTypes 属性检验规则
- mixins 组件间公用方法
4.2 初次创建组件时调用
- getInitialState 得到初始状态对象
- render 返回组件树. 必须设置
- componentDidMount 渲染到 dom 树中是调用,只在客户端调用,可用于获取原生节点
4.3 组件的属性值改变时调用
- componentWillReceiveProps 属性改变调用
- shouldComponentUpdate 判断是否需要重新渲染
- render 返回组件树. 必须设置
- componentDidUpdate 渲染到 dom 树中是调用, 可用于获取原生节点
4.4 销毁组件
- componentWillUnmount 组件从 dom 销毁前调用
4.5 示例诠释组件全生命周期
import React, { Component } from 'react'; class LifeCycle extends Component { props = { value: '开始渲染' } componentWillReceiveProps(nextProps){ console.log('componentWillReceiveProps'); this.setState({ value: nextProps.value }); } shouldComponentUpdate(nextProps,nextState){ console.log('shouldComponentUpdate'); return true; } componentWillUpdate(nextProps,nextState){ console.log('componentWillUpdate'); } componentWillMount(){ console.log('componentWillMount'); } render() { console.log('render'); return <span>{this.props.value}</span> } componentDidMount() { console.log('componentDidMount'); } componentDidUpdate(prevProps,prevState) { console.log('componentDidUpdate'); } componentWillUnmount(prevProps,prevState) { console.log('componentWillUnmount'); } } export default LifeCycle;
调用组件并销毁组件示例
import React, { Component } from 'react'; import LifeCycleDemo from './LifeCycleDemo'; class DestroyComponent extends Component { state = { value:1, destroyed:false } increase = () => { this.setState({ value: this.state.value + 1 }); } destroy = () => { this.setState({ destroyed: true }); } render() { if(this.state.destroyed){ return null; } return <div> <p> <button onClick={this.increase}>每次加1</button> <button onClick={this.destroy}>干掉这两个按钮</button> </p> <LifeCycleDemo value={this.state.value}/> </div>; } } export default DestroyComponent;
4.6 回顾组件的渲染过程
# 创建-》渲染-》销毁 getDefaultProps() getInitialState() componentWillMount() render() componentDidMount() componentWillUnmount() # 更新组件 componentWillReceiveProps() shouldComponentUpdate() componentWillUpdate() render() componentDidUpdate()
5. 使用ref对操作DOM
- ReactDOM.findDOMNode
- this.refs.xxx
获取DOM后可以方便结合现有非 react 类库的使用,通过 ref/refs 可以取得组件实例,进而取得原生节点,不过尽量通过 state/props 更新组件,不要使用该功能去更新组件的DOM。
import React, { Component } from 'react'; import ReactDOM, { findDOMNode } from 'react-dom'; class HandleDOMComponent extends Component { componentDidMount(){ // 两种方式都可以获取到元素 let ele = findDOMNode(this.refs.content); let ele2 = this.refs.content; // 如果想用 jquery,那么这是个好时机 console.log( ele ); console.log( ele.innerHTML ); console.log( ele2.innerHTML ); } render(){ return ( <div> <h3>来吧,一起操作DOM</h3> <div ref='content'>这是我DOM元素里面的内容</div> </div> ); } } export default HandleDOMComponent;
再看一个有趣的例子
import React, { Component } from 'react'; import { findDOMNode } from 'react-dom'; class Refs extends Component { state = { red: 0, green: 0, pink: 0 } update = (e) => { this.setState({ red: findDOMNode(this.refs.red).value, green: findDOMNode(this.refs.green).value, pink: findDOMNode(this.refs.pink).value }) } render(){ return ( <div> <Slider ref="red" update={this.update} /> {this.state.red} <br /> <Slider ref="green" update={this.update} /> {this.state.green} <br /> <Slider ref="pink" update={this.update} /> {this.state.pink} </div> ) } } class Slider extends Component { render(){ return ( <input type="range" min="0" max="255" onChange={this.props.update} /> ) } } export default Refs;
6. 事件event
可以通过设置原生 dom 组件的 onEventType 属性来监听 dom 事件,例如 onClick, onMouseDown,在加强组件内聚性的同时,避免了传统 html 的全局变量污染
'use strict'; import React, { Component } from 'react'; class HandleEvent extends Component { state = { liked: false } handleClick = (event) => { this.setState({liked: !this.state.liked}); } render() { let text = this.state.liked ? '喜欢' : '不喜欢'; return ( <p onClick={this.handleClick}> 我 {text} 你. </p> ); } } export default HandleEvent;
注意:事件回调函数参数为标准化的事件对象,可以不用考虑 IE
更多事件我们可以一起看这里
7. 组件的组合
7.1 受限组件 && 非受限组件
受限组件示例:
render() { return <input type="text" value="Hello!" />; }
非受限组件示例:
render() { return <input type="text" />; }
7.2 使用自定义的组件
'use strict'; import React, { Component } from 'react'; class ComponentA extends Component { render() { return <a href='#'>我是组件A<br/></a> } } class ComponentB extends Component { render() { return <a href='#'>我是组件B</a> } } class SelfCreateComponent extends Component { render() { return ( <i> <ComponentA /> <ComponentB /> </i> ); } } export default SelfCreateComponent;
7.3 组合 CHILDREN
自定义组件中可以通过 this.props.children 访问自定义组件的子节点
'use strict'; import React, { Component } from 'react'; // 定义一个组件,通过React.Children 拿到组件里面的子元素 class ListComponent extends Component { render(){ return <ul> { React.Children.map( this.props.children, function(c){ return <li>{c}</li>; }) } </ul> } } class UseChildrenComponent extends Component { render(){ return ( <ListComponent> <a href="#">Facebook</a> <a href="#">Google</a> <a href="#">SpaceX</a> </ListComponent> ) } } export default UseChildrenComponent;
8. form表单操作
8.1 React表单组件和 html 的不同点
- value/checked 属性设置后,用户输入无效
- textarea 的值要设置在 value 属性
<textarea name="description" value="This is a description." />
- select 的 value 属性可以是数组,不建议使用 option 的 selected 属性
<select multiple={true} value={['B', 'C']}> <option value="A">Apple</option> <option value="B">Banana</option> <option value="C">Cranberry</option> </select>
- input/textarea 的 onChange 用户每次输入都会触发(即使不失去焦点)
- radio/checkbox/option 点击后触发 onChange
8.2 综合表达组件示例
1.定义复选框组件Checkboxes
import React, { Component } from 'react'; class Checkboxes extends Component { render(){ return <span> A <input onChange={this.props.handleCheck} name="goodCheckbox" type="checkbox" value="A"/> B <input onChange={this.props.handleCheck} name="goodCheckbox" type="checkbox" value="B" /> C <input onChange={this.props.handleCheck} name="goodCheckbox" type="checkbox" value="C" /> </span> } } export default Checkboxes;
2.定义单选框按钮组RadioButtons
import React, { Component } from 'react'; class RadioButtons extends Component { saySomething(){ alert("我是一个很棒的单选框按钮组"); } render(){ return <span> A <input onChange={this.props.handleRadio} name="goodRadio" type="radio" value="A"/> B <input onChange={this.props.handleRadio} name="goodRadio" type="radio" defaultChecked value="B"/> C <input onChange={this.props.handleRadio} name="goodRadio" type="radio" value="C"/> </span> } } export default RadioButtons;
3.FormApp组件集成两个组件并处理表单逻辑
'use strict'; import React, { Component } from 'react'; import Checkboxes from './Checkboxes'; import RadioButtons from './RadioButtons'; class FormApp extends Component { state = { inputValue: '请输入...', selectValue: 'A', radioValue:'B', checkValues:[], textareaValue:'请输入...' } handleSubmit = (e) => { e.preventDefault(); let formData = { input: this.refs.goodInput.value, select: this.refs.goodSelect.value, textarea: this.refs.goodTextarea.value, radio: this.state.radioValue, check: this.state.checkValues, } alert('您即将提交表单') console.log('你提交的数据是:') console.log(formData); } handleRadio = (e) => { this.setState({ radioValue: e.target.value, }) } handleCheck = (e) => { let checkValues = this.state.checkValues.slice(); let newVal = e.target.value; let index = checkValues.indexOf(newVal); if( index == -1 ){ checkValues.push( newVal ) }else{ checkValues.splice(index,1); } this.setState({ checkValues: checkValues, }) } render(){ return <form onSubmit={this.handleSubmit}> <h3> 请输入内容 </h3> <input ref="goodInput" type="text" defaultValue={this.state.inputValue }/> <br/> <h3> 请选择 </h3> <select defaultValue={ this.state.selectValue } ref="goodSelect"> <option value="A">A</option> <option value="B">B</option> <option value="C">C</option> <option value="D">D</option> <option value="E">E</option> </select> <br/> <h3> 单项选择 </h3> <RadioButtons ref="goodRadio" handleRadio={this.handleRadio} /> <br/> <h3> 多选按钮 </h3> <Checkboxes handleCheck={this.handleCheck} /> <br/> <h3> 反馈建议 </h3> <textarea defaultValue={this.state.textareaValue} ref="goodTextarea"></textarea> <br/> <button type="submit">确认提交</button> </form> } } export default FormApp;
9. mixin共享
mixin 是一个普通对象,通过 mixin 可以在不同组件间共享代码,使你的React程序变得更为可重用。
注意,ES6语法不支持mixin写法,而是可以通过decorator去实现代码共享,这里使用ES5语法做示例说明。
import React from 'react'; var SetIntervalMixin = { componentWillMount: function() { this.intervals = []; }, setInterval: function() { this.intervals.push(setInterval.apply(null, arguments)); }, componentWillUnmount: function() { this.intervals.forEach(clearInterval); } }; var MixinDemo = React.createClass({ // Use the mixin mixins: [SetIntervalMixin], getInitialState: function() { return {seconds: 0}; }, componentDidMount: function() { // Call a method on the mixin this.setInterval(this.tick, 1000); }, tick: function() { this.setState({seconds: this.state.seconds + 1}); }, render: function() { return ( <p> 计时器已经运行了: {this.state.seconds} 秒. </p> ); } }); export default MixinDemo;
那么,接下来,我们用high-order component的方式来实现mixin:
import React, { Component } from 'react'; let Mixin = MixinComponent => class extends Component { constructor() { super(); this.state = { val: 0 }; this.update = this.update.bind(this); } update(){ this.setState({val: this.state.val + 1}); } componentWillMount(){ console.log('will mount...') } render(){ return ( <MixinComponent update={this.update} {...this.state} {...this.props} /> ) } componentDidMount(){ console.log('mounted...') } } const Button = (props) => { return ( <button onClick={props.update}> {props.txt} - {props.val} </button> ) } const Label = (props) => { return ( <label onMouseMove={props.update}> {props.txt} - {props.val} </label> ) } let ButtonMixed = Mixin(Button); let LabelMixed = Mixin(Label); class Mixins extends Component { render(){ return ( <div> <ButtonMixed txt="button" /> <LabelMixed txt="label" /> </div> ) } } export default Mixins;
基础部分完结寄语
总结起来,学习的难度不高,以上内容掌握后,基本能够进行React的开发,后续我们继续相关内容的讲解,内容预告:一步步教你如何用React写一个简单完整的应用功能。