这本书的主要内容都是以react v0.12为主,ES5语法,笔记中将会使用react v0.14和RS6。
第1章 react简介
1.本质上是一个状态机,它以精简的模型管理复杂的随着时间而变化的状态。
2.它不是model-view-controller,它是mvc中的v(view),用来渲染视图。
3.React运用虚拟的dom实现了一个强大的渲染系统。
4.(1)React就像高性能3d游戏引擎,以渲染函数为基础。这些函数读入当前的状态,将其转换为目标页面上的一个虚拟表现。只要react被告知状态有变化,它就会重新运行这些函数,计算页面的一个新的虚拟表现,接着自动化把结果转换成必要的dom更新来反应新的表现。
4.(2)乍一看,这种方式应该比通常的javascript方案(按需更新每一个元素要慢),但react确实是这么做的:它是用了非常高效的算法,计算出虚拟页面当前版本和新版间的差异,基于这些差异对dom进行必要的最少更新。
5.所以说react赢就赢在了:
- 最小化了重绘,
- 并避免了不必要的dom操作,
- 这两点都是公认的性能瓶颈。
6.react的虚拟表示差异算法,不但能够把这些问题的影响降到最低。 ,还能简化应用的维护成本。当用户输入或者有其他更新导致状态改变时,我们只要简单的通知react状态改变了,它就能自动化地处理剩下的事情。我们无须深入到详细的过程之中。
7.react在整个应用中只使用单个事件处理器,并且会把所有的事件委托到这个处理器上。这一点也提升了react的性能。因为如果有很多时间处理器也会导致性能问题。
第2章 JSX
什么是JSX
JSX即JavaScript XML——一种在React组件内部构建标签的类XML语法。
与以往在JavaScript中嵌入HTML标签的几种方案相比,JSX有如下几点明显的特征:
- JSX是一种句法变换——每一个JSX节点都对应着一个JavaScript函数
- JSX既不提供也不需要运行时库。
- JSX并没有改变会添加JavaScript的语义——它只是简单的函数调用而已。
使用JSX前
React.DOM.h1({className: 'question'}, 'Question'); // v0.11React.createElement('h1', {className: 'question'}, 'Question'); // v0.12
使用JSX之后
<h1 className="question">Queations</h1>;
使用JSX的好处
- 允许使用熟悉的语法来定义html元素树
- 提供更佳语义化且易懂的标签
- 程序结构更容易被直观化
- 抽象了react element的创建过程
- 可以随时掌控html标签以及生成这些标签的代码
- 是原生的javascript
定义一个自定义组件
const React = require('react');
const ReactDOM = require('react-dom');
class Divider extends React.Component {render() {return (<div classNams="divider"><h2>Questions</h2><hr/></div>)}
}
ReactDOM.render(<Divider></Divider>,document.getElementById('init')
);
使用动态值
JSX将两个花括号之间的内容{。。。}渲染为动态值。
简单值:
var text = "Questions"
class Divider extends React.Component {render() {return (<div classNams="divider"><h2>{text}</h2><hr/></div>)}
}
ReactDOM.render(<Divider></Divider>,document.getElementById('init')
);
复杂逻辑使用函数求值:
class Date extends React.Component {
dataToString = (d) => {return [d.getFullYear(),d.getMonth + 1,d.getDate()].join('-')}render() {return (<h2>{this.dataToString(new Date())}</h2>)}
}
ReactDOM.render(<Date></Date>,document.getElementById('init')
);
react通过将数组中的每一个元素渲染为一个节点的方式对数组进行自动求值。
var text = ['hello', 'world']
class Hello extends React.Component {render() {return (<h2>{text}</h2>)}
}
ReactDOM.render(<Hello></Hello>,document.getElementById('init')
);
// 渲染为 <h2>helloworld</h2>
子节点
将上面介绍过的例子改写为:
const React = require('react');
const ReactDOM = require('react-dom');
class Divider extends React.Component {render() {return (<div classNams="divider"><h2>{this.props.children}</h2><hr/></div>)}
}
ReactDOM.render(<Divider>Questions</Divider>,document.getElementById('init')
);
注意,后面的示例中我们不会再写
const React = require('react');const ReactDOM = require('react-dom');ReactDOM.render(<Divider>Questions</Divider>,document.getElementById('init'));
这几行代码。
React将开始标签与结束标签之间的所有子节点保存在一个名为this.props.children的特殊组件属性中。在上面的例子中,this.props.children == ["Questions"]
条件判断
if/else逻辑很难用HTML标签来表达,直接往JSX中加入if语句会渲染出无效的JavaScript:
// 这是错误的示范
<div className={if(isComplete) { 'is-complete' }}>。。。</div>
而解决的办法就是使用以下某种方法:
- 使用三目运算符
- 设置一个变量并在属性中引用
- 将逻辑转化到函数中
- 使用&&运算符
使用三目运算符
render(){return(<div className={this.state.isComplete ? 'is-complete' : ''}>...</div>)
}
使用变量
getIsComplete = () => {return this.state.isComplete ? 'is-complete' : '';
}
render(){var isComplete = this.getIsComplete();return(<div className={isComplete}>...</div>)
}
使用函数
getIsComplete = () => {return this.state.isComplete ? 'is-complete' : '';
}
render(){return(<div className={this.getIsComplete()}>...</div>)
}
使用逻辑与(&&)运算符
render(){return(<div className={this.state.isComplete && 'is-complete'}>...</div>)
}
非dom属性值
key
- 通过给组件设置一个独一无二的键,并确保它在一个渲染周期中保持一致,使得react能够更智能地决定应该重用一个组件,还是销毁并重新创建一个组件,进而提升渲染性能。
ref
- ref允许父组件在render方法之外保持对子组件的一个引用
- 在jsx中,你可以通过在属性中设置期望的引用名来定义一个引用。
render () {return (<div><input ref="myInput" .../></div>);
}
接着就可以在组件中的任何地方使用this.refs.myInput获取这个引用了(不是真实的dom,是react在需要时用来创建dom的一个描述对象。若要访问真实的节点,则可以使用:this.refs.myInput.getDOMNode() )
dangerouslySetInnerHTML(随时可能发生变化)
render () {var htmlString = {__html: "<span>an html string</span>"}return (<div dangerouslySetInnerHTML={htmlString}></div>);
}
事件
在jsx中,捕获一个事件就像给组件的方法设置一个属性一样简单。
handleClick = (event) => {//...
}
render () {return (<div onClick={this.handleClick.bind(this)}></div>);
}
注释
jsx的本质就是javascript,所以可以在标签内添加原生的js注释。 注释可以用以下2种形式添加:
- 1.当做一个元素的子节点
- 2.内联在元素的属性中
1.作为子节点:
<!-- 子节点形式的注释只需要简单地包裹在花括号内即可,并且可以跨多行。-->
<div>{/* 这是注释内容 */}<input type="text" name="" value="" placeholder="">
</div>
2.作为内联属性
// 内联的注释可以有两种形式。首先,可以使用多行注释:
<div><input/*这是多行注释内容!*/type="text" placeholder="address" />
</div>// 其次也可以使用单行注释
<div><inputtype="text" // 这是单行注释placeholder="address" />
</div>
特殊属性
- htmlFor (for)
- 由于jsx会转换为js,所以有些关键词我们不能用,比如这个。
<label htmlFor="for-text" ...>
- className (class)
<div className={classes} ...>
样式
简单的驼峰属性命名即可
render () {var styles = {borderColor: "#888",borderThinkness: "1px"};return (<div style={styles}></div>);
}
如果需要添加浏览器前缀瑞 -webkit-、-ms- 大驼峰(除了 ms ), 如:
var divStyle = {WebkitTransition: 'all', // 'W' 是大写msTransition: 'all' // 'ms' 为小写
};
没有jsx的react
所有的jsx标签最后都会被转换为原生的javascript。因此jsx对于react来说并不是必需的。然而,jsx确实减少了一部分复杂性。如果你不打算在react中使用jsx,那么在react中创建元素时需要知道以下三点:
- 1.定义组件类
- 2.创建一个为组件类产生实例的工厂
- 3.使用工厂来创建ReactElement实例
page 19.
第3章 组件的生命周期
React为每个组件提供了生命周期钩子函数去响应不同的时刻:
- 1.实例化(创建时)
- 2.活动期(存在期)
- 3.销毁时(被销毁)
生命周期方法
react组件以简洁的生命周期api来单单提供你所需要的方法(而不是所有方法)。
实例化
一个实例初次被创建所调用的生命周期方法和其他各个后续实例被创建时所调用的方法略有不同。
所以:当你首次使用一个组件类时,有如下调用顺序:
- getDefaultProps
- getInitialState
- componentWillMount
- render
- componentDidMount
而该组件类的所有后续应用,将会如下:
- getDefaultProps
- componentWillMount
- render
- componentDidMount
⚠️区别在于后续的不必执行:getInitialState函数,其余函数如常。(有待进一步理解)
React在ES6的实现中去掉了getInitialState这个hook函数,规定state在constructor中实现,如下:
class App extends React.Component {constructor(props) {super(props);this.state = {};}...}
Babel的Blog上还有一种实现方法,即直接使用赋值语句:
Class App extends React.Component {
constructor(props) {super(props);
}state = {}
...}
存在期
随着应用状态的改变,以及组件逐渐受到影响,依次调用如下方法:
- componentWillReceiveProps
- shouldComponentUpdate
- componentWillUpdate
- render
- componentDidUpdate
销毁&清理期
最后,组件被使用完后,componentWillUnmount方法将会被调用,目的是给这个实例提供清理自身的机会。
接下来,应该具体的理解 p24~30的内容(各个时期的各个函数的具体运作)。这里不过多阐述以增加篇幅,只理清思路即可。
我认为这段内容,应该是react的第一块关键内容,乃设计之精心之处,应多多揣摩,烂熟于胸。
由于原书的内容是基于ES5的,下面的章节对使用ES6时的生命周期进行讲解:
第二章 - ES6版 React组件生命周期小结
下面所写的,只适合前端的React。(React也支持后端渲染,而且和前端有点小区别,不过我没用过。)
相关函数
简单地说,React Component通过其定义的几个函数来控制组件在生命周期的各个阶段的动作。
在ES6中,一个React组件是用一个class来表示的(具体可以参考官方文档),如下:
// 定义一个TodoList的React组件,通过继承React.Component来实现
class TodoList extends React.Component {...
}
这几个生命周期相关的函数如下:
constructor(props, context)
构造函数,在创建组件的时候调用一次。
componentWillMount()
在组件挂载之前调用一次。如果在这个函数里面调用setState,本次的render函数可以看到更新后的state,并且只渲染一次。
componentDidMount()
在组件挂载之后调用一次。这个时候,子主键也都挂载好了,可以在这里使用refs。
componentWillReceiveProps(nextProps)
props是父组件传递给子组件的。父组件发生render的时候子组件就会调用componentWillReceiveProps(不管props有没有更新,也不管父子组件之间有没有数据交换)。
shouldComponentUpdate(nextProps, nextState)
组件挂载之后,每次调用setState后都会调用shouldComponentUpdate判断是否需要重新渲染组件。默认返回true,需要重新render。在比较复杂的应用里,有一些数据的改变并不影响界面展示,可以在这里做判断,优化渲染效率。
componentWillUpdate(nextProps, nextState)
shouldComponentUpdate返回true或者调用forceUpdate之后,componentWillUpdate会被调用。
componentDidUpdate()
除了首次render之后调用componentDidMount,其它render结束之后都是调用componentDidUpdate。
componentWillMount、componentDidMount和componentWillUpdate、componentDidUpdate可以对应起来。区别在于,前者只有在挂载的时候会被调用;而后者在以后的每次更新渲染之后都会被调用。
ReactElement render()
render是一个React组件所必不可少的核心函数(上面的其它函数都不是必须的)。记住,不要在render里面修改state。
componentWillUnmount()
组件被卸载的时候调用。一般在componentDidMount里面注册的事件需要在这里删除。
更新方式
在react中,触发render的有4条路径。
以下假设shouldComponentUpdate都是按照默认返回true的方式。
- 首次渲染Initial Render
- 调用this.setState (并不是一次setState会触发一次render,React可能会合并操作,再一次性进行render)
- 父组件发生更新(一般就是props发生改变,但是就算props没有改变或者父子组件之间没有数据交换也会触发render)
- 调用this.forceUpdate
下面是我对React组件四条更新路径地总结:
注意,如果在shouldComponentUpdate里面返回false可以提前退出更新路径。
一个React组件生命周期的测试例子
代码比较简单,没有逻辑,只是在每个相关函数里面alert一下。
点击链接来试试这个例子。
源码
const React = require('react');
const ReactDOM = require('react-dom');
class LifeCycle extends React.Component {constructor(props) {super(props);alert("Initial render");alert("constructor");this.state = {str: "hello"};}componentWillMount() {alert("componentWillMount");}componentDidMount() {alert("componentDidMount");}componentWillReceiveProps(nextProps) {alert("componentWillReceiveProps");}shouldComponentUpdate() {alert("shouldComponentUpdate");return true; // 记得要返回true}componentWillUpdate() {alert("componentWillUpdate");}componentDidUpdate() {alert("componentDidUpdate");}componentWillUnmount() {alert("componentWillUnmount");}setTheState() {let s = "hello";if (this.state.str === s) {s = "HELLO";}this.setState({str: s});}forceItUpdate() {this.forceUpdate();}render() {alert("render");return(<div><span>{"Props:"}<h2>{parseInt(this.props.num)}</h2></span><br /><span>{"State:"}<h2>{this.state.str}</h2></span></div>);}
}class Container extends React.Component {constructor(props) {super(props);this.state = {num: Math.random() * 100};}propsChange() {this.setState({num: Math.random() * 100});}setLifeCycleState() {this.refs.rLifeCycle.setTheState();}forceLifeCycleUpdate() {this.refs.rLifeCycle.forceItUpdate();}unmountLifeCycle() {// 这里卸载父组件也会导致卸载子组件React.unmountComponentAtNode(document.getElementById("container"));}parentForceUpdate() {this.forceUpdate();}render() {return (<div><a href="javascript:;" className="weui_btn weui_btn_primary" onClick={this.propsChange.bind(this)}>propsChange</a><a href="javascript:;" className="weui_btn weui_btn_primary" onClick={this.setLifeCycleState.bind(this)}>setState</a><a href="javascript:;" className="weui_btn weui_btn_primary" onClick={this.forceLifeCycleUpdate.bind(this)}>forceUpdate</a><a href="javascript:;" className="weui_btn weui_btn_primary" onClick={this.unmountLifeCycle.bind(this)}>unmount</a><a href="javascript:;" className="weui_btn weui_btn_primary" onClick={this.parentForceUpdate.bind(this)}>parentForceUpdateWithoutChange</a><LifeCycle ref="rLifeCycle" num={this.state.num}></LifeCycle></div>);}
}ReactDOM.render(<Container></Container>,document.getElementById('init')
);
示例解读:
⚠️所有的弹窗都是在子组件中的
- 首次加载:Initial render、constructor、componentWillMount、render、componentDidMount
- 改变父的state:componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render、componentDidUpdate
- 改变子的state:shouldComponentUpdate、componentWillUpdate、render、componentDidUpdate
- 父forceUpdate:componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render、componentDidUpdate(和2一样)
- 子forceUpdate:componentWillUpdate、render、componentDidUpdate
- 卸载父:componentWillUnmount