最近项目中在用到react,这里就整理一下react相关的基础知识。
安装
如果使用npm的话,需要安装两个基础的npm包,react和react-dom。
第一个组件
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14
 | class ShoppingList extends React.Component {     render () {         return (             <div className="shopping-list">                 <h1>{this.porps.name}</h1>                 <ul>                     <li>显示器</li>                     <li>主机</li>                     <li>机械键盘</li>                 </ul>             </div>        )     } }
 | 
上面创建了一个最基本的react组件,其中render函数中用到了JSX的语法。
JSX仅仅是js的语法糖,其最终还是要被转换成js的,可以使用webpack+babel-loader。
一个react组件接收一个参数props,然后通过render函数返回一个reactNode对象,注意是一个,而不能是多个。
接下来就可以这样使用ShoppingList组件:
| 1 2 3 4
 | ReactDom.render(     <ShoppingList shoppingList='购物清单'/>,     document.querySelect('#app') );
 | 
可以发现props在调用组件的时候的传入,就好比我们调用函数时传入参数一样。
props
上面的组件是一个无状态(stateless)组件,只能接收props,返回reactNode。
所以,它可以简写为如下形式:
| 1 2 3 4 5 6 7 8 9 10
 | const ShoppingList = (props) => (     <div className="shopping-list">         <h1>{this.porps.shoppingList}</h1>         <ul>             <li>显示器</li>             <li>主机</li>             <li>机械键盘</li>         </ul>     </div> );
 | 
这种react组件被称为functional component。
另外还有以下几点需要注意:
- props是只读的
- 所有的组件必须表现地像pure function,不可以修改props。
state
当然,既然有stateless组件,肯定也有stateful组件。
stateful的组件就不能定义为functional component,而必须是class component。后面将要介绍的包含生命周期函数(lifecycle hooks)的组件也只能定义为class component。
看这个时钟的案例:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
 | class Clock extend React.Component {     constructor (props){         super(props);             this.state = {             date: new Date()         }     }     componentDidMount () {             this.timerId = serInterval(             () => this.tick(),             1000         );     }     componentWillUnmount () {             clearInterval(this.timerId);     }     tick () {         this.setState({             date: new Date()         });     }     render () {         return (             <div>                 <h1>当前时间为:</h1>                 <p>{this.state.date}</p>             </div>        );     } } 
 ReactDom.render(     <Clock/>,     document.querySelector('#app') );
 | 
这个组件的运行过程如下:
- 在通过ReactDom.render来渲染Clock组件到DOM时,调用constructor()。
- 接着调用render()并更新DOM。
- 将Clock挂载到DOM后,调用componentDidMount(),它开启了一个定时器,让浏览器每隔1s调用tick()。
- 1s后,执行tick(),并且在tick中执行setState()。
- state的改变会触发render()函数的再次调用(注意这时constructor,componentDidMount不会再调用)。
- 如果Clock组件被从DOM中移除,那么会调用componentWillUnmount(),停止定时器。
另外使用state时,还有以下几点需要注意:
- 不要直接修改state对象,而要通过this.setState()。
| 1 2 3 4 5 6 7
 | this.state.commment = 'hello'; this.setState({     comment: 'hello' });
 | 
- state和props更新可能异步的,因为react出于性能考虑,可能将多个setState()调用打包到一起来执行。所以我们不能依赖与它当前的值来计算下一个state。
| 1 2 3 4 5 6 7 8
 | this.setState({     counter: this.state.counter + this.props.increment }); this.setState((prevState, props) => ({     counter: prevState.counter + props.increment }));
 | 
- state对象的更新是通过merge来实现的。所以,你调用setState的时候,只需要改变你关心的state分片就可以。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 | constructor (props) {     super(props);     this.state = {         posts: [],         comments: []     } } componentDidMount () {     fetchPost().then(response => {         this.setState({             posts: response.posts         });     });     fetchComment().then(response => {         this.setState({             comments: response.comments         });     }); }
 | 
- state的初始化,即可以在class component中的constructor()中使用- this.state来赋值,也可以在使用- React.createClass()中提供- gitInitialState()方法来初始化。
 
- 在应用中,所有数据的变化都源于一个- source for truth,通常就是state。如果多个子组件都需要这份数据,可以将state放在离这些子组件最近的公共祖先组件中,在祖先组件中,通过props将state(分片)分发给各个子组件,这也是react中数据流自上而下流动的体现。
 
组件生命周期
一个React组件的声明周期大致分为三个阶段:
- 挂载阶段(Mounting),发生在组件被挂载到真实DOM时
- 更新阶段(Updating),组件重新渲染成选你DOM并决定真实DOM是否需要更新时
- 卸载阶段(Unmounting),组件从DOM中卸载时
下图列出了在这三个阶段对应的方法以及执行顺序:

尤其注意各个lifecycle hooks在什么阶段会执行,比如constructor()只会在挂载阶段执行,更新阶段不执行。
列表项的key
我们常常有这样的需求,根据一个数组,渲染出一个列表。
这时,我们必须为每个列表项提供一个独一无二的属性key,React可以用这个key来鉴别出哪个列表项被增加、修改或者删除了。
| 1 2 3 4 5 6 7 8 9 10 11
 | render () {     const numbers = [1,2,3,4,5];     const items = numbers.map((num) => (         <li key={num.toString()}>num</li>     ));     return (         <ul>             {items}         </ul>     ); }
 | 
注意:在哪里生成reactNode数组,就在哪里使用key。
在原生的HTML中,<input>,<textarea>,select这些表单元素维持着自己的状态value,并且根据用户的输入来自动更新value。
而在react中,所有组件的状态应该在构造函数中声明,并且只能够通过setValue来更新状态。
所以,在react中的表单元素会是下面这个样子。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
 | class InfoForm extends React.Component {     constructor (props) {         super(props);         this.state = {             title: 'react',             type: 'frontend',             content: ''         }     }     handleChange (event) {         const target = event.target;         const value = target.value;         const name = target.name;             this.setState({             [name]: value         });     }     handleSubmit (event) {         event.preventDefault();         console.log(this.state);     }     render () {         return (             <form onSubmit={this.handleSubmit.bind(this)}>                 <label>                     标题:                     <input name='title' type='text' value={this.state.title} onChange={this.handleChange.bind(this)}/>                 </label>                 <br/>                 <label>                     类型:                     <select name='type' value={this.state.type} onChange={this.handleChange.bind(this)}>                         <option value="frontend">前端</option>                         <option value="backend">后台</option>                         <option value="other">其他类型</option>                     </select>                 </label>                 <br/>                 <label>                     内容:                     <textarea name='content' value={this.state.content} onChange={this.handleChange.bind(this)}/>                 </label>                 <br/>                 <input type='submit' value='提交'/>             </form>         )     } }
 | 
合成还是继承?
React给出的答案是合成(composition),而非继承(inheritance)。在React中,可以通过props提供了很强大的合成组件的功能。
props.children
我们常常有这样的需求,一个组件在定义的时候并不知道具体的内容是什么,比如SideBar或者Dialog。
这时候,我们使用特殊的props.children来定义该组件:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
 | let SideBar = props => (     <div className="sidebar">         {props.children}     </div> ) 
 // 调用组件 ReactDom.render(     <SideBar>         <ul>             <li>item1</li>             <li>item2</li>             <li>item3</li>         </ul>     </SideBar> );
 | 
多个slot
如果我们想定义一个包含多个插槽(slot)的组件,那么我们可以通过props来实现。
因为props可以向组件传递任何类型的数据,包括对象、函数、ReactNode。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 | let SplitPane = props => (     <div className="split-pane">         <div className="split-pane-left">             {this.props.left}         </div>         <div className="split-pane-right">             {this.props.right}         </div>     </div> ); 
 // 调用组件 ReactDom.render(     <SplitPane         left={             <Contracts/>         }         right={             <Chat/>         }     > );
 | 
thinking in react
- 组件架构方式有两种:从整体到局部和从局部到整体。在简单的应用中,推荐从整体到局部,在复杂的应用中,推荐从局部到整体。 
- 让你的app只保存最小量的state数据。比如在todoList中,state中保存了todos的一个数组,就不需要保存todos数组的长度。因为这可以根据todos数组计算出来。 
- 把app所需的所有数据罗列出来,并根据以下3条原则来判定哪些数据要当作state: 
- 如果该数据可以通过props传递给其他组件,那么可能不是state。
- 如果该数据一直保持不变,可能不是state。
- 如果该数据可以根据其他state或者props计算得到,可能不是state。
最后
以上就是react的基础部分,后面再整理react的进阶知识以及redux,react-redux的实际应用。