都是去年年初准备的,继上一篇草稿以后的css
React 官方在早期为 JSX 语法解析开发了一套编译器 JSTransform,目前已经再也不维护,
如今已所有采用 Babel 的 JSX 编译器实现。由于二者在功能上彻底重复,
而 Babel 做为专门的JavaScript语法编译工具,提供了更为强大的功能,达到了“一处配置,统一运行”的目的。html
除了使用 JSX 语法,咱们还可使用 React.createElement() 和 React.cloneElement() 来构建 React 元素。前端
2.1 虚拟元素能够理解为真实元素的对应,它的构建与更新都是在内存中完成的,并不会真正渲染到 DOM 中去。
在 React 中建立的虚拟元素能够分为两类,
DOM 元素(DOM element)与组件元素(component element),分别对应着原生 DOM 元素与自定义元素react
元素类型: DOM 元素和组件元素,
对应规则是 HTML 标签首字母是否为小写字母,其中小写首字母对应 DOM 元素,而组件元素天然对应大写首字母;
JSX 还能够经过命名空间的方式使用组件元素,以解决组件相同名称冲突的问题,或是对一组组件进行归类webpack
2.1.1 DOM 元素git
2.1.2 组件元素github
2.2 JSX 基本语法web
class 属性改成 className;编程
for 属性改成 htmlFor;
写自定义属性的时候,都由标准写法改成小驼峰写法 ;数组
Boolean 属性:要传 false 时,必须使用属性表达式。
例如,<Checkbox checked={true} /> 能够简写为 <Checkbox checked />, 反之 <Checkbox checked={false} /> 就能够省略 checked 属性。 复制代码
展开属性:可使用 ES6 rest/spread 特性来提升效率:
const data = { name: 'foo', value: 'bar' }; const component = <Component name={data.name} value={data.value} />; 能够写成: const data = { name: 'foo', value: 'bar' }; const component = <Component {...data} />; 复制代码
自定义 HTML 属性
若是在 JSX 中往 DOM 元素中传入自定义属性,React 是不会渲染的: <div d="xxx">content</div> 若是要使用 HTML 自定义属性,要使用 data- 前缀,这与 HTML 标准也是一致的: <div data-attr="xxx">content</div> 然而,在自定义标签中任意的属性都是被支持的: <x-my-component custom-attr="foo" /> 以 aria- 开头的网络无障碍属性一样能够正常使用: <div aria-hidden={true}></div> 不论组件是用什么方法来写,咱们都须要知道,组件的最终目的是输出虚拟元素,也就是需 要被渲染到界面的结构。其核心渲染方法,或称为组件输出方法,就是 render 方法 复制代码
属性值要使用表达式,只要用 {} 替换 "" 便可;
<Person name={window.isLoggedIn ? window.name : ''} />; 复制代码
React 会将全部要显示到 DOM 的字符串转义,防止 XSS。因此,若是 JSX 中含有转义后的 实体字符,好比 ©(©),则最后 DOM 中不会正确显示,由于 React 自动把 © 中的特 殊字符转义了。有几种解决办法: - 直接使用 UTF-8 字符 ©; - 使用对应字符的 Unicode 编码查询编码; - 使用数组组装 `<div>{['cc ', <span>©</span>, ' 2015']}</div>`; - 直接插入原始的 HTML。 此外,React 提供了 dangerouslySetInnerHTML 属性。正如其名,它的做用就是避免 React 转 义字符,在肯定必要的状况下可使用它: <div dangerouslySetInnerHTML={{__html: 'cc © 2015'}} /> 复制代码
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> ); } }); 复制代码
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> ); } } 复制代码
使用无状态函数构建的组件称为无状态组件,这种构建方式是 0.14 版本以后新增的,且官方颇为推崇。示例代码以下: function Button({ color = 'blue', text = 'Confirm' }) { return ( <button className={`btn btn-${color}`}> <em>{text}</em> </button> ); } 无状态组件只传入 props 和 context 两个参数;也就是说,它不存在 state,也没有生命周 期方法,组件自己即上面两种 React 组件构建方法中的 render 方法; 它建立时始终保持了一个实例,避免了没必要要的检查和内存分配,作到了内部优化; 复制代码
ReactDOM 中的 API 很是少,只有 findDOMNode、unmountComponentAtNode 和 render。
import React, { Component } from 'react'; import ReactDOM from 'react-dom'; class App extends Component { componentDidMount() { // myComp 是 Comp 的一个实例,所以须要用 findDOMNode 转换为相应的 DOM const myComp = this.refs.myComp; const dom = findDOMNode(myComp); } render() { return ( <div> <Comp ref="myComp" /> </div> ); } } 复制代码
React 合成事件的事件类型是 JavaScript 原生事件类型的一个子集。
<button onClick={this.handleClick}>Test</button>
在 JSX 中,咱们必须使用驼峰的形式来书写事件的属性名(好比
onClick),而 HTML 事件则须要使用所有小写的属性名(好比 onclick)。
React 并不会像 DOM0 级事件那样将事件处理器直接绑定到 HTML 元素之上。React 仅仅是
借鉴了这种写法而已
复制代码
在 React 底层,主要对合成事件作了两件事:事件委派和自动绑定。
在使用 React 事件前,必定要熟悉它的事件代理机制。
它并不会把事件处理函数直接绑定到真实的节点上,而是把全部事件绑定到结构的最外层,
使用一个统一的事件监听器,这个事件监听器上维持了一个映射来保存全部组件内部的事件监听和处理函数。
当组件挂载或卸载时,只是在这个统一的事件监听器上插入或删除一些对象;
当事件发生时,首先被这个统一的事件监听器处理,而后在映射里找到真正的事件处理函数并调用。
这样作简化了事件处理和回收机制,效率也有很大提高;
在 React 组件中,每一个方法的上下文都会指向该组件的实例,即自动绑定 this 为当前组件。
并且 React 还会对这种引用进行缓存,以达到 CPU 和内存的最优化。
在使用 ES6 classes 或者纯函数时,这种自动绑定就不复存在了,咱们须要手动实现 this 的绑定。
bind 方法。这个方法能够帮助咱们绑定事件处理器内的 this ,并能够向事件处理器中传递参数,好比:
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 已经实现了该提案。好比:
——————————————————————————————————————————
① ECMAScrip This-Binding Syntanx,详见 github.com/zenparsing/…
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,
所以咱们不须要再对它使用 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> } } 使用上述几种方式,都可以实如今类定义的组件中绑定 this 上下文的效果。 复制代码
3.在 React 中使用原生事件
React 提供了很好用的合成事件系统,但这并不意味着在 React 架构下没法使用原生事件。 React 提供了完备的生命周期方法,其中 componentDidMount 会在组件已经完成安装而且在浏览器 中存在真实的 DOM 后调用,此时咱们就能够完成原生事件的绑定;
import React, { Component } from 'react'; class NativeEventDemo extends Component { componentDidMount() { this.refs.button.addEventListener('click', e => { this.handleClick(e); }); } handleClick(e) { console.log(e); } componentWillUnmount() { this.refs.button.removeEventListener('click'); } render() { return <button ref="button">Test</button>; } } 值得注意的是,在 React 中使用 DOM 原生事件时,必定要在组件卸载时手动移除, 不然极可能出现内存泄漏的问题。 而使用合成事件系统时则不须要,由于 React 内部已经帮你妥善地处理了; 复制代码
尽可能避免在 React 中混用合成事件和原生 DOM 事件,另外,用 reactEvent.nativeEvent.stopPropagation() 来阻止冒泡是不行的。 阻止 React 事件冒泡的行为只能用于 React合成事件系统中,且没办法阻止原生事件的冒泡。 反之,在原生事件中的阻止冒泡行为,却能够阻止 React 合成事件的传播;
实际上,React 的合成事件系统只是原生 DOM 事件系统的一个子集。它仅仅实现了 DOM Level 3 的事件接口,而且统一了浏览器间的兼容问题。有些事件 React 并无实现,
或者受某些限制没办法去实现,好比 window 的 resize 事件。
对于没法使用 React 合成事件的场景,咱们还须要使用原生事件来完成
浏览器原生 DOM 事件的传播能够分为 3 个阶段:事件捕获阶段、目标对象自己的事件处理程序调用以及事件冒泡。
React的合成事件则并无实现事件捕获,仅仅支持了事件冒泡机制;
阻止原生事件传播须要使用 e.preventDefault(),不过对于不支持该方法的浏览器(IE9 如下),只能使用 e.cancelBubble = true 来阻止。而在 React 合成事件中,只须要使用 e.preventDefault() 便可。
8.受控组件和非受控组件
每当表单的状态发生变化时,都会被写入到组件的 state 中,这种组件在React 中被称为受控组件(controlled component)。
在受控组件中,组件渲染出的状态与它的 value 或 checked prop 相对应; 若是一个表单组件没有 value props(单选按钮和复选框对应的是 checked prop)时,就能够称为非受控组件;
不提倡在 React 中使用非受控组件
9.样式处理
const style = { color: 'white', backgroundImage: `url(${imgUrl})`, // 注意这里大写的 W,会转换成 -webkit-transition WebkitTransition: 'all', // ms 是惟一小写的浏览器前缀 msTransition: 'all', }; const component = <Component style={style} />; 复制代码
当设置 width 和 height 这类与大小有关的样式时,大部分会以像素为单位,此时若重复输 入 px,会很麻烦。为了提升效率,React 会自动对这样的属性添加 px。好比:
// 渲染成 height: 10px
const style = { height: 10 };
给组件动态设置 className
render() { const btnClass = classNames({ 'btn': true, 'btn-pressed': this.state.isPressed, 'btn-over': !this.state.isPressed && this.state.isHovered, }); return <button className={btnClass}>{this.props.label}</button>; } 复制代码
启用 CSS Modules 的代码以下: // webpack.config.js css?modules&localIdentName=[name]__[local]-[hash:base64:5] 加上 modules 即为启用,其中 localIdentName 是设置生成样式的命名规则 复制代码CSS Modules 实现了如下几点:
使用了 CSS Modules 后,就至关于给每一个 class 名外加了 :local,以此来实现样式的局部化。 若是咱们想切换到全局模式,可使用 :global 包裹。示例代码以下: .normal { color: green; } /* 以上与下面等价 */ :local(.normal) { color: green; } /* 定义全局样式 */ :global(.btn) { color: red; } /* 定义多个全局样式 */ :global { .link { color: green; } .box { color: yellow; } } 复制代码下面是具体项目中使用的 webpack 部分配置代码:
module: { loaders: [{ test: /\.jsx?$/, loader: 'babel', }, { test: /\.scss$/, exclude: path.resolve(__dirname, 'src/styles'), loader: 'style!css?modules&localIdentName=[name]__[local]!sass?sourceMap=true', }, { test: /\.scss$/, include: path.resolve(__dirname, 'src/styles'), loader: 'style!css!sass?sourceMap=true', }] } /* src/app.js */ import './styles/app.scss'; import Component from './view/Component' /* src/views/Component.js */ import './Component.scss'; 目录结构以下: src ├── app.js ├── styles │ ├── app.scss │ └── normalize.scss └── views ├── Component.js └── Component.scss 这样全部全局的样式都放到 src/styles/app.scss 中引入就能够了,其余全部目录(包括 src/views)中的样式都是局部的。 CSS Modules 很好地解决了 CSS 目前面临的模块化难题 复制代码
10.组件间通讯
11.组件间抽象
在 React 组件的构建过程当中,经常有这样的场景,有一类功能须要被不一样的组件公用,此时就涉及抽象的话题。
在不一样的设计理念下,有许多的抽象方法,而针对 React,咱们重点讨论两种:mixin 和高阶组件;
11.1 mixin(混入)
React 在使用 createClass 构建组件时提供了 mixin 属性,好比官方封装的 PureRenderMixin: import React from 'react'; import PureRenderMixin from 'react-addons-pure-render-mixin'; React.createClass({ mixins: [PureRenderMixin], render() { return <div>foo</div>; } }); 在 createClass 对象参数中传入数组 mixins,里面封装了咱们所须要的模块 复制代码
破坏了原有组件的封装
命名冲突
增长复杂性
复制代码
针对这些困扰,React 社区提出了新的方式来取代 mixin,那就是高阶组件
高阶组件(higher-order component),相似于高阶函数,它接受 React 组件做为输入,输出一个新的 React 组件;
实现高阶组件的方法有以下两种。
import React, { Component } from 'React'; const MyContainer = (WrappedComponent) => class extends Component { render() { return <WrappedComponent {...this.props} />; } } 复制代码
const MyContainer = (WrappedComponent) => class extends WrappedComponent { render() { return super.render(); } } 复制代码
12.组件性能优化
,咱们都知道影响网页性能最大的因素是浏览器的重绘(reflow)和 重排版(repaint)。React 背后的 Virtual DOM 就是尽量地减小浏览器的重绘与重排版。 对于性能优化这个主题,咱们每每会基于“不信任”的前提,即咱们须要提升 React Virtual DOM 的效率。从 React 的渲染过程来看,如何防止不避要的渲染多是最须要去解决的问题。 然而,针对这个问题,React 官方提供了一个便捷的方法来解决,那就是 PureRender。
12.1 纯函数
12.2 PureRender
12.3 Immutable
12.4 key
12.5 react-addons-perf
React 对测试有完善的支持,目前比较完善的 React 测试框架有 Jest 和 Enzyme
Enzyme 是由 Airbnb 开源的 React 组件测试框架。与 Jest 相比,Enzyme 提供相似 jQuery 操做 DOM 的语法,在作测试断言时更灵活、易用