- JSX
- 注释:在一个组件的子元素位置使用注释要用 {} 包起来
-
const App = ( <Nav> {/* 节点注释 */} <Person /* 多行 注释 */ name={window.isLoggedIn ? window.name : ''} /> </Nav> );
但 HTML 中有一类特殊的注释——条件注释,它常用于判断浏览器的版本:
-
<!--[if IE]> <p>Work in IE browser</p> <![endif]-->
上述方法可以通过使用 JavaScript 判断浏览器版本来替代:
-
{ (!!window.ActiveXObject || 'ActiveXObject' in window) ? <p>Work in IE browser</p> : '' }
-
-
元素属性
-
class 属性改为 className
-
for 属性改为 htmlFor
-
- 注释:在一个组件的子元素位置使用注释要用 {} 包起来
-
React 组件的构建
- React 组件即为组件元素
- React 组件基本上由 3 个部分组成——属性(props)、状态(state)以及生命周期方法
- React 组件可以接收参数,也可能有自身状态。一旦接收到的参数或自身状态有所改变,React 组件就会执行相应的生命周期方法,最后渲染。整个过程完全符合传统组件所定义的组件职责。
- React 组件的构建方法
-
React.createClass
-
-
const Button = React.createClass({ getDefaultProps() { return { color: 'blue', text: 'Confirm', }; }, render() { const { color, text } = this.props; return ( <button className={`btn btn-${color}`}> <em>{text}</em> </button> ); } });
- 在 0.14 版本发布之前,这一直都是 React 官方唯一指定的组件写法
-
只用写成 <Button />,就可以被解析成 React.createElement(Button) 方法来创建 Button
实例,这意味着在一个应用中调用几次 Button,就会创建几次 Button 实例
-
-
-
ES6 classes
-
-
import React, { Component } from 'react'; class Button extends Component { constructor(props) { super(props); } static defaultProps = { color: 'blue', text: 'Confirm', }; render() { const { color, text } = this.props; return ( <button className={`btn btn-${color}`}> <em>{text}</em> </button> ); } }
React 的所有组件都继承自顶层类 React.Component。它的定义非常简洁,只是初始化了
React.Component 方法,声明了 props、context、refs 等,并在原型上定义了 setState 和
forceUpdate 方法。内部初始化的生命周期方法与 createClass 方式使用的是同一个方法
创建的。
-
-
-
无状态函数(stateless function)
-
-
function Button({ color = 'blue', text = 'Confirm' }) { return ( <button className={`btn btn-${color}`}> <em>{text}</em> </button> ); }
-
无状态组件只传入 props 和 context 两个参数;也就是说,它不存在 state,也没有生命周
期方法,组件本身即上面两种 React 组件构建方法中的 render 方法。不过,像 propTypes 和
defaultProps 还是可以通过向方法设置静态属性来实现的。 -
在适合的情况下,我们都应该且必须使用无状态组件。无状态组件不像上述两种方法在调用
时会创建新实例,它创建时始终保持了一个实例,避免了不必要的检查和内存分配,做到了内部
优化。
-
-
-
-
React 数据流
- 在 React 中,数据是自顶向下单向流动的,即从父组件到子组件
- state 与 props 是 React 组件中最重要的概念
-
如果顶层组件初始化 props,那么 React 会向下遍历整棵组件树,重新尝试渲染所有相关的子组件。
-
而 state 只关心每个组件自己内部的状态,这些状态只能在组件内改变。
-
把组件看成一个函数,那么它接受了 props 作为参数,内部由 state 作为函数的内部参数,返回一个 Virtual DOM 的实现
- state: setState
- 是异步操作函数;
- 组件在还没有渲染之前, this.setState 还没有被调用;
- 批量执行 State 转变时让 DOM 渲染更快(相对比一个一个的setState的来的快)。
- props 本身是不可变的:组件的 props 一定来自于默认属性或通过父组件传递而来
-
如果说要渲染一个对 props 加工后的值,最简单的方法就是使用局部变量或直接在 JSX 中计算结果
- React 为 props 同样提供了默认配置,通过 defaultProps 静态变量的方式来定义
-
React.Children 是 React 官方提供的一系列操作 children 的方法。它提供诸如 map、forEach、count 等实用函数,可以为我们处理子组件提供便利
-
-
组件 props
- 现在我们发现对于 state 来说,它的通信集中在组件内部;对于 props 来说,它的通信是父组
件向子组件的传播。
- 现在我们发现对于 state 来说,它的通信集中在组件内部;对于 props 来说,它的通信是父组
- propTypes:用于规范 props 的类型与必需的状态
-
React 生命周期
- 挂载、渲染和卸载这
-
import React, { Component, PropTypes } from 'react'; class App extends Component { static propTypes = { // ... }; static defaultProps = { // ... }; constructor(props) { super(props); this.state = { // ... }; } componentWillMount() { // ... } componentDidMount() { // ... } render() { return <div>This is a demo.</div>; } }
-
componentWillMount 方法会在 render 方法之前执行,而 componentDidMount 方法会在 render 方法之后执行,这些都只会在组件初始化时运行一次
- 组件卸载非常简单,只有 componentWillUnmount 这一个卸载前状态
-
import React, { Component, PropTypes } from 'react'; class App extends Component { componentWillUnmount() { // ... } render() { return <div>This is a demo.</div>; }
在 componentWillUnmount 方法中,我们常常会执行一些清理方法,如事件回收或是清除定
时器。
- 数据更新过程
-
更新过程指的是父组件向下传递 props 或组件自身执行 setState 方法时发生的一系列更新动作
-
import React, { Component, PropTypes } from 'react'; class App extends Component { componentWillReceiveProps(nextProps) { // this.setState({}) } shouldComponentUpdate(nextProps, nextState) { // return true; } componentWillUpdate(nextProps, nextState) { // ... } componentDidUpdate(prevProps, prevState) { // ... } render() { return <div>This is a demo.</div>; } }
如果组件自身的 state 更新了,那么会依次执行 shouldComponentUpdate、componentWillUpdate 、
render 和 componentDidUpdate。 - 需要注意的是,你不能在 componentWillUpdate 中执行 setState
- 如果组件是由父组件更新 props 而更新的,那么在 shouldComponentUpdate 之前会先执行
componentWillReceiveProps 方法。此方法可以作为 React 在 props 传入后,渲染之前 setState 的
机会。在此方法中调用 setState 是不会二次渲染的。
-
-
React 与 DOM
- ReactDOM
-
findDOMNode
-
DOM 真正被添加到 HTML 中的生命周期方法是 componentDidMount 和 componentDidUpdate 方法
-
假设要在当前组件加载完时获取当前 DOM,则可以使用 findDOMNode:
import React, { Component } from 'react'; import ReactDOM from 'react-dom'; class App extends Component { componentDidMount() { // this 为当前组件的实例 const dom = ReactDOM.findDOMNode(this); } render() {} }
-
findDOMNode 只对已经挂载的组件有效。
-
-
render
- 为什么说只有在顶层组件我们才不得不使用 ReactDOM 呢?这是因为要把 React 渲染的 Virtual DOM 渲染到浏览器的 DOM 当中,就要使用 render 方法了:
-
ReactComponent render( ReactElement element, DOMElement container, [function callback] )
-
该方法把元素挂载到 container 中,并且返回 element 的实例(即 refs 引用)。当然,如果是无状态组件,render 会返回 null。当组件装载完毕时,callback 就会被调用。
-
当组件在初次渲染之后再次更新时,React 不会把整个组件重新渲染一次,而会用它高效的 DOM diff 算法做局部的更新。这也是 React 最大的亮点之一!
-
- 为什么说只有在顶层组件我们才不得不使用 ReactDOM 呢?这是因为要把 React 渲染的 Virtual DOM 渲染到浏览器的 DOM 当中,就要使用 render 方法了:
-
refs
-
- ReactDOM
- 事件系统
-
在 JSX 中,我们必须使用驼峰的形式来书写事件的属性名(比如onClick),而 HTML 事件则需要使用全部小写的属性名(比如 onclick)。
-
HTML 的属性值只能是 JavaScript 代码字符串,而在 JSX 中,props 的值则可以是任意类型,这里是一个函数指针。
-
在 React 底层,主要对合成事件做了两件事:事件委ี和自动绑定。
- 事件委派
- React并不会把事件处理函数直接绑定到真实的节点上,而是把所有事件绑定到结构的最外层,使用一个统一的事件监听器,这个事件监听器上维持了一个映射来保存所有组件内部的事件ᄢ听和处理函数。
-
当组件挂载或卸载时,只是在这个统一的事件ᄢ听器上插入或删除一些对象;当事件发生时,首先被这个统一的事件ᄢ听器处理,然后在映射里找到真正的事件处理函数并调用。这样做简化了事件处理和回收机制,效率也有很大提升。
-
自动绑定
- 在 React 组件中,每个方法的上下文都会指向该组件的实例,即自动绑定 this 为当前组件
- 而且 React 还会对这种引用进行缓存,以达到 CPU 和内存的最优化
-
在使用 ES6 classes 或者纯函数时,这种自动绑定就不复存在了,我们需要手动实现 this 的绑定。
-
bind 方法
-
import React, { Component } from 'react'; class App extends Component { handleClick(e, arg) { console.log(e, arg); } render() { // 通过bind方法实现,可以传递参数 return <button onClick={this.handleClick.bind(this, 'test')}>Test</button>; } }
-
-
如果方法只绑定,不传参,那 stage 0 ᕘ案中提供了一个便的方案①——Ԥ冒号语法,其作用与 this.handleClick.bind(this) 一致,并且 Babel 已经实现了该提案。
-
import React, { Component } from 'react'; class App extends Component { handleClick(e) { console.log(e); } render() { return <button onClick={::this.handleClick}>Test</button>; } }
-
- 构造器内声明
- 在组件的构造器内完成了 this 的绑定,这种绑定方式的好处在于仅需要进行一次绑定,而不需要每次调用事件ᄢ听器时去执行绑定操作:
-
import React, { Component } from 'react'; class App extends Component { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); } handleClick(e) { console.log(e); } render() { return <button onClick={this.handleClick}>Test</button>; } }
-
- 在组件的构造器内完成了 this 的绑定,这种绑定方式的好处在于仅需要进行一次绑定,而不需要每次调用事件ᄢ听器时去执行绑定操作:
- 箭头函数
-
箭头函数不仅是函数的“语法糖”,它还自动绑定了定义此函数作用域的 this,因此我们不需要再对它使用 bind 方法。
-
import React, { Component } from 'react'; class App extends Component { const handleClick = (e) => { console.log(e); }; render() { return <button onClick={this.handleClick}>Test</button>; } } 或 import React, { Component } from 'react'; class App extends Component { handleClick(e) { console.log(e); } render() { return <button onClick={() => this.handleClick()}>Test</button> } }
-
-
-
在 React中使用原生事件
-
componentDidMount 会在组件已经完成安装并且在浏览器中存在真实的 DOM 后调用,此时我们就可以完成原生事件的绑定。
-
值得注意的是,在 React 中使用 DOM 原生事件时,一定要在组件卸载时手动移除,否则很可能出现内存泄漏的问题。而使用合成事件系统时则不需要,因为 React 内部已经帮你妥ؒ地处理了。
-
-
合成事件与原生事件混用
- 不要将合成事件与原生事件混用,如:
-
componentDidMount() { document.body.addEventListener('click', e => { this.setState({ active: false, }); }); document.querySelector('.code').addEventListener('click', e => { e.stopPropagation(); }) } componentWillUnmount() { document.body.removeEventListener('click'); document.querySelector('.code').removeEventListener('click'); }
-
-
通过 e.target判断来避免
-
componentDidMount() { document.body.addEventListener('click', e => { if (e.target && e.target.matches('div.code')) { return; } this.setState({ active: false, }); }); }
-
- 不要将合成事件与原生事件混用,如:
-
对比React合成事件与JavaScript原生事件
-
事件传播与阻止事件传播
-
-
- 非受控组件
- 如果一个表单组件没有value props(单选按钮和复选框对应的是checked prop)时,就可以称为非受控组件。
- 非受控组件是一种反模式,它的值不受组件自身的state或props控制。通常要通过为其添加ref prop来访问渲染后的底层DOM元素
- 对比受控组件和非受控组件
- 受控组件
-
<input value={this.state.value} onChange={e => { this.setState({ value: e.target.value.toUpperCase() }) }} />
-
-
非受控组件
-
<input defaultValue={this.state.value} onChange={e => { this.setState({ value: e.target.value.toUpperCase() }) }} />
-
- 如果不对受控组件绑定change事件,我们在文本框中输入任何值都不会起作用。多数情况下,对于非受控组件,我们并不需要提供change事件。
- 最大的区别:非受控组件的状态并不会受应用状态的控制,应用中也多了局部组件状态,而受控组件的值来自于组件的state.
- 受控组件
- 事件委派
- mixin:
-
import React, { Component } from 'React';
import { mixin } from 'core-decorators';
const PureRender = { shouldComponentUpdate() {} }; const Theme = { setTheme() {} }; @mixin(PureRender, Theme) class MyComponent extends Component { render() {} } - 这个mixin与createClass中的mixin的区别:
- 之前直接给对象的prototype属性赋值,但这里用了getOwnPropertyDescriptor 和defineProperty 这两个方法
- 这样实现的好处在于 defineProperty 这个方法,也就是定义与复制的区别,定义是对已有的定义,赋值则是覆盖已有的定义。
-
-
- 初探React 生命周期
- 当首次挂载组件时,按顺序执行 getDefaultProps、getInitialState、componentWillMount、 render 和 componentDidMount。
- 当卸载组件时,执行 componentWillUnmount。
- 当重新挂载组件时,此时按顺序执行 getInitialState、componentWillMount、render 和 componentDidMount,但并不执行 getDefaultProps。
- 当再次渲染组件时,组件接受到更新状态,此时按顺序执行 componentWillReceiveProps、 shouldComponentUpdate、componentWillUpdate、render 和 componentDidUpdate。
- 生命周期原理
- 通过 mountComponent 挂载组件,初始化序号、标记等参数,判断是否为无状态组件,并进行对应的组件初始化工作,比如初始化 props、context 等参数。利用 getInitialState 获取初始化state、初始化更新对列和更新状态。
- 若存在 componentWillMount,则执行。如果此时在 componentWillMount 中调用 setState 方法,是不会触发 re-render的,而是会进行 state 合并,且 inst.state = this._processPendingState
(inst.props, inst.context) 是在 componentWillMount 之后执行的,因此 componentWillMount 中的 this.state 并不是最新的,在 render 中才可以获取更新后的 this.state。 -
mountComponent 本质上是通过递归渲染内容的,由于递归的特性,父组件的componentWillMount 在其子组件的 componentWillMount 之前调用,而父组件的 componentDidMount在其子组件的 componentDidMount 之后调用。
- 禁止在 shouldComponentUpdate 和 componentWillUpdate 中调用 setState,这会造成循环调用,直至耗光浏览器内存后崩溃。
-
如果存在 componentWillUnmount,则执行并重置所有相关参数、更新队列以及更新状态,如果此时在 componentWillUnmount 中调用 setState,是不会触发 re-render 的,这是因为所有更新队列和更新状态都被重置为 null,并清除了公共类,完成了组件卸载操作。
- React diff 算法的 3 个策略
- 策略一: Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计
- 策略二:拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会
生成不同的树形结构。 - 策略三:对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。
- 基于以上策略, React 分别对 tree diff、 component diff 以及 element diff 进行算法优化。
- tree diff
- 基于策略一, React 对树的算法进行了简洁明了的优化,即对树进行分层比较,两棵树只会
对同一层次的节点进行比较 - 既然 DOM 节点跨层级的移动操作少到可以忽略不计,针对这一现象,React 通过 updateDepth
对 Virtual DOM 树进行层级控制,只会对相同层级的 DOM 节点进行比较,即同一个父节点下的
所有子节点。当发现节点已经不存在时,则该节点及其子节点会被完全删除掉,不会用于进一步
的比较。这样只需要对树进行一次遍历,便能完成整个 DOM 树的比较。
- 基于策略一, React 对树的算法进行了简洁明了的优化,即对树进行分层比较,两棵树只会
- component diff
- 如果是同一类型的组件,按照原策略继续比较 Virtual DOM 树即可。
- 如果不是,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点。
- 对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切知道这点,那
么就可以节省大量的 diff 运算时间。因此, React 允许用户通过 shouldComponentUpdate()
来判断该组件是否需要进行 diff 算法分析。
- 如果是同一类型的组件,按照原策略继续比较 Virtual DOM 树即可。
- element diff
- 当节点处于同一层级时, diff 提供了 3 种节点操作,分别为 INSERT_MARKUP(插入)、 MOVE_
EXISTING(移动)和 REMOVE_NODE(删除)。- INSERT_MARKUP: 新的组件类型不在旧集合里,即全新的节点,需要对新节点执行插入操作。
- MOVE_EXISTING:旧集合中有新组件类型,且 element 是可更新的类型, generateComponentChildren 已调用 receiveComponent,这种情况下 prevChild=nextChild,就需要做移动操作,可以复用以前的 DOM 节点。
- REMOVE_NODE:旧组件类型,在新集合里也有,但对应的 element 不同则不能直接复用和更
新,需要执行删除操作,或者ே组件不在新集合里的,也需要执行删除操作。
- 当节点处于同一层级时, diff 提供了 3 种节点操作,分别为 INSERT_MARKUP(插入)、 MOVE_
- tree diff
- React 将 Virtual DOM 树转换成 actual DOM 树的最少操作的过程称为调和