react tutorial

can发布于2018/11/23

注脚

展开查看详情

1.

2.React 入门教程 目錄 介紹 0 React 概览 1 开发环境配置 2 Webpack 2.1 JSX 3 使用 JSX 3.1 属性扩散 3.2 和 HTML 的差异 3.3 组件 4 组件生命周期 4.1 事件处理 4.2 DOM 操作 4.3 组合组件 4.4 组件间通信 4.5 Mixins 4.6 Data Flow 5 Flux 5.1 Redux 5.2 进化 Flux 5.2.1 Redux 基础 5.2.2 和 React 配合使用 5.2.3 [Redux 进阶] 5.2.4 表单 6 [动画] 7 [测试] 8 [性能调优] 9 服务端渲染 10 2

3.React 入门教程 React 入门教程 按照惯例,在介绍一个新技术之前总是要为它背书的,作为 React 受众在开始接触 之前肯定会有一些喜闻乐见的疑问: 为什么不用 Backbone? 为什么不用 Angular? ... 在没有真正使用之前,其实没法评价哪一个好,没有最好的,只有最合适的,如 Why React 所说,Give it five minutes,希望你能克服初次遇到 JSX 这种存在的偏 见去尝试一下。 因为官方文档组织得比较散乱,希望本教程能成为一个不错的入门参考。 有任何问题 → Github 本文档对应 React v0.14.x,使用 ES6。 介紹 3

4.React 入门教程 React 概览 React 的核心思想是:封装组件。 各个组件维护自己的状态和 UI,当状态变更,自动重新渲染整个组件。 基于这种方式的一个直观感受就是我们不再需要不厌其烦地来回查找某个 DOM 元 素,然后操作 DOM 去更改 UI。 React 大体包含下面这些概念: 组件 JSX Virtual DOM Data Flow 这里通过一个简单的组件来快速了解这些概念,以及建立起对 React 的一个总体认 识。 import React, { Component } from 'react'; import { render } from 'react-dom'; class HelloMessage extends Component { render() { return <div>Hello {this.props.name}</div>; } } // 加载组件到 DOM 元素 mountNode render(<HelloMessage name="John" />, mountNode); 组件 React 应用都是构建在组件之上。 React 概览 4

5.React 入门教程 上面的 HelloMessage 就是一个 React 构建的组件,最后一句 render 会把这 个组件显示到页面上的某个元素 mountNode 里面,显示的内容就是 <div>Hello John</div> 。 props 是组件包含的两个核心概念之一,另一个是 state (这个组件没用 到)。可以把 props 看作是组件的配置属性,在组件内部是不变的,只是在调用 这个组件的时候传入不同的属性(比如这里的 name )来定制显示这个组件。 JSX 从上面的代码可以看到将 HTML 直接嵌入了 JS 代码里面,这个就是 React 提出的 一种叫 JSX 的语法,这应该是最开始接触 React 最不能接受的设定之一,因为前 端被“表现和逻辑层分离”这种思想“洗脑”太久了。但实际上组件的 HTML 是组成一 个组件不可分割的一部分,能够将 HTML 封装起来才是组件的完全体,React 发明 了 JSX 让 JS 支持嵌入 HTML 不得不说是一种非常聪明的做法,让前端实现真正意 义上的组件化成为了可能。 好消息是你可以不一定使用这种语法,后面会进一步介绍 JSX,到时候你可能就会 喜欢上了。现在要知道的是,要使用包含 JSX 的组件,是需要“编译”输出 JS 代码 才能使用的,之后就会讲到开发环境。 Virtual DOM 当组件状态 state 有更改的时候,React 会自动调用组件的 render 方法重新 渲染整个组件的 UI。 当然如果真的这样大面积的操作 DOM,性能会是一个很大的问题,所以 React 实 现了一个Virtual DOM,组件 DOM 结构就是映射到这个 Virtual DOM 上,React 在 这个 Virtual DOM 上实现了一个 diff 算法,当要重新渲染组件的时候,会通过 diff 寻找到要变更的 DOM 节点,再把这个修改更新到浏览器实际的 DOM 节点上,所 以实际上不是真的渲染整个 DOM 树。这个 Virtual DOM 是一个纯粹的 JS 数据结 构,所以性能会比原生 DOM 快很多。 Data Flow React 概览 5

6.React 入门教程 “单向数据绑定”是 React 推崇的一种应用架构的方式。当应用足够复杂时才能体会 到它的好处,虽然在一般应用场景下你可能不会意识到它的存在,也不会影响你开 始使用 React,你只要先知道有这么个概念。 React 概览 6

7.React 入门教程 开发环境配置 要搭建一个现代的前端开发环境配套的工具有很多,比如 Grunt / Gulp / Webpack / Broccoli,都是要解决前端工程化问题,这个主题很大,这里为了使用 React 我们 只关注其中的两个点: JSX 支持 ES6 支持 好消息是业界领先的 ES6 编译工具 Babel 随着作者被 Facebook 招入麾下,已经 内置了对 JSX 的支持,我们只需要配置 Babel 一个编译工具就可以了,配合 webpack 非常方便。 开发环境配置 7

8.React 入门教程 Webpack 配置 React 开发环境 Webpack 是一个前端资源加载/打包工具,只需要相对简单的配置就可以提供前端 工程化需要的各种功能,并且如果有需要它还可以被整合到其他比如 Grunt / Gulp 的工作流。 安装 Webpack: npm install -g webpack Webpack 使用一个名为 webpack.config.js 的配置文件,要编译 JSX,先安装 对应的 loader: npm install babel-loader --save-dev 假设我们在当前工程目录有一个入口文件 entry.js ,React 组件放置在一个 components/ 目录下,组件被 entry.js 引用,要使用 entry.js ,我们把 这个文件指定输出到 dist/bundle.js ,Webpack 配置如下: var path = require('path'); module.exports = { entry: './entry.js', output: { path: path.join(__dirname, '/dist'), filename: 'bundle.js' }, resolve: { extensions: ['', '.js', '.jsx'] }, module: { loaders: [ { test: /\.js|jsx$/, loaders: ['babel'] } ] } } resolve 指定可以被 import 的文件后缀。比如 Hello.jsx 这样的文件就可 以直接用 import Hello from 'Hello' 引用。 Webpack 8

9.React 入门教程 loaders 指定 babel-loader 编译后缀名为 .js 或者 .jsx 的文件,这样你就 可以在这两种类型的文件中自由使用 JSX 和 ES6 了。 监听编译: webpack -d --watch 更多关于 Webpack 的介绍 webpack-howto Webpack 9

10.React 入门教程 JSX 为什么要引入 JSX 这种语法 传统的 MVC 是将模板放在其他地方,比如 <script> 标签或者模板文件,再在 JS 中通过某种手段引用模板。按照这种思路,想想多少次我们面对四处分散的模 板片段不知所措?纠结模板引擎,纠结模板存放位置,纠结如何引用模板……下面 是一段 React 官方的看法: We strongly believe that components are the right way to separate concerns rather than "templates" and "display logic." We think that markup and the code that generates it are intimately tied together. Additionally, display logic is often very complex and using template languages to express it becomes cumbersome. 简单来说,React 认为组件才是王道,而组件是和模板紧密关联的,组件模板和组 件逻辑分离让问题复杂化了。显而易见的道理,关键是怎么做? 所以就有了 JSX 这种语法,就是为了把 HTML 模板直接嵌入到 JS 代码里面,这样 就做到了模板和组件关联,但是 JS 不支持这种包含 HTML 的语法,所以需要通过 工具将 JSX 编译输出成 JS 代码才能使用。 JSX 是可选的 因为 JSX 最终是输出成 JS 代码来表达的,所以我们可以直接用 React 提供的这些 DOM 构建方法来写模板,比如一个 JSX 写的一个链接: <a href="http://facebook.github.io/react/">Hello!</a> 用 JS 代码来写就成这样了: React.createElement('a', {href: 'http://facebook.github.io/react/'}, JSX 10

11.React 入门教程 你可以通过 React.createElement 来构造组件的 DOM 树。第一个参数是标签 名,第二个参数是属性对象,第三个参数是子元素。 一个包含子元素的例子: var child = React.createElement('li', null, 'Text Content'); var root = React.createElement('ul', { className: 'my-list' }, child); React.render(root, document.body); 对于常见的 HTML 标签,React 已经内置了工厂方法: var root = React.DOM.ul({ className: 'my-list' }, React.DOM.li(null, 'Text Content') ); 所以 JSX 和 JS 之间的转换也很简单直观,用 JSX 的好处就是它基本上就是 HTML(后面会讲到有一些小差异),对于构造 DOM 来说我们更熟悉,更具可读 性。 关于 JSX 映射成 JS 对象,也就是 Virtual DOM 的内部描述,参见Virtual DOM Terminology,如果你不想使用 JSX,直接使用 JS 就是用这里面提到的接口方法。 JSX 11

12.React 入门教程 使用 JSX 利用 JSX 编写 DOM 结构,可以用原生的 HTML 标签,也可以直接像普通标签一 样引用 React 组件。这两者约定通过大小写来区分,小写的字符串是 HTML 标 签,大写开头的变量是 React 组件。 使用 HTML 标签: import React from 'react'; import { render } from 'react-dom'; var myDivElement = <div className="foo" />; render(myDivElement, document.getElementById('mountNode')); HTML 里的 class 在 JSX 里要写成 className ,因为 class 在 JS 里是保 留关键字。同理某些属性比如 for 要写成 htmlFor 。 使用组件: import React from 'react'; import { render } from 'react-dom'; import MyComponent from './MyComponet'; var myElement = <MyComponent someProperty={true} />; render(myElement, document.body); 使用 JavaScript 表达式 属性值使用表达式,只要用 {} 替换 "" : 使用 JSX 12

13.React 入门教程 // Input (JSX): var person = <Person name={window.isLoggedIn ? window.name : ''} /> // Output (JS): var person = React.createElement( Person, {name: window.isLoggedIn ? window.name : ''} ); 子组件也可以作为表达式使用: // Input (JSX): var content = <Container>{window.isLoggedIn ? <Nav /> : <Login />}</ // Output (JS): var content = React.createElement( Container, null, window.isLoggedIn ? React.createElement(Nav) : React.createElement(Login) ); 注释 在 JSX 里使用注释也很简单,就是沿用 JavaScript,唯一要注意的是在一个组件的 子元素位置使用注释要用 {} 包起来。 使用 JSX 13

14.React 入门教程 var content = ( <Nav> {/* child comment, put {} around */} <Person /* multi line comment */ name={window.isLoggedIn ? window.name : ''} // end of line /> </Nav> ); HTML 转义 React 会将所有要显示到 DOM 的字符串转义,防止 XSS。所以如果 JSX 中含有 转义后的实体字符比如 &copy; (©) 最后显示到 DOM 中不会正确显示,因为 React 自动把 &copy; 中的特殊字符转义了。有几种解决办法: 直接使用 UTF-8 字符 © 使用对应字符的 Unicode 编码,查询编码 使用数组组装 <div>{['cc ', <span>&copy;</span>, ' 2015']}</div> 直接插入原始的 HTML <div dangerouslySetInnerHTML={{__html: 'cc &copy; 2015'}} /> 自定义 HTML 属性 如果在 JSX 中使用的属性不存在于 HTML 的规范中,这个属性会被忽略。如果要 使用自定义属性,可以用 data- 前缀。 可访问性属性的前缀 aria- 也是支持的。 支持的标签和属性 使用 JSX 14

15.React 入门教程 如果你要使用的某些标签或属性不在这些支持列表里面就可能被 React 忽略,必须 要使用的话可以提 issue,或者用前面提到的 dangerouslySetInnerHTML 。 使用 JSX 15

16.React 入门教程 属性扩散 有时候你需要给组件设置多个属性,你不想一个个写下这些属性,或者有时候你甚 至不知道这些属性的名称,这时候 spread attributes 的功能就很有用了。 比如: var props = {}; props.foo = x; props.bar = y; var component = <Component {...props} />; props 对象的属性会被设置成 Component 的属性。 属性也可以被覆盖: var props = { foo: 'default' }; var component = <Component {...props} foo={'override'} />; console.log(component.props.foo); // 'override' 写在后面的属性值会覆盖前面的属性。 关于 ... 操作符 The ... operator (or spread operator) is already supported for arrays in ES6. There is also an ES7 proposal for Object Rest and Spread Properties. 属性扩散 16

17.React 入门教程 JSX 与 HTML 的差异 除了前面提到的 class 要写成 className ,比较典型的还有: style 属性接受由 CSS 属性构成的 JS 对象 onChange 事件表现更接近我们的直觉(不需要 onBlur 去触发) 表单的表现差异比较大,要单独再讲 更多异同,可以参见 DOM Differences 和 HTML 的差异 17

18.React 入门教程 React 组件 可以这么说,一个 React 应用就是构建在 React 组件之上的。 组件有两个核心概念: props state 一个组件就是通过这两个属性的值在 render 方法里面生成这个组件对应的 HTML 结构。 注意:组件生成的 HTML 结构只能有一个单一的根节点。 props 前面也提到很多次了, props 就是组件的属性,由外部通过 JSX 属性传入设 置,一旦初始设置完成,就可以认为 this.props 是不可更改的,所以不要轻易 更改设置 this.props 里面的值(虽然对于一个 JS 对象你可以做任何事)。 state state 是组件的当前状态,可以把组件简单看成一个“状态机”,根据状态 state 呈现不同的 UI 展示。 一旦状态(数据)更改,组件就会自动调用 render 重新渲染 UI,这个更改的动 作会通过 this.setState 方法来触发。 划分状态数据 一条原则:让组件尽可能地少状态。 这样组件逻辑就越容易维护。 什么样的数据属性可以当作状态? 组件 18

19.React 入门教程 当更改这个状态(数据)需要更新组件 UI 的就可以认为是 state ,下面这些可 以认为不是状态: 可计算的数据:比如一个数组的长度 和 props 重复的数据:除非这个数据是要做变更的 最后回过头来反复看几遍 Thinking in React,相信会对组件有更深刻的认识。 无状态组件 你也可以用纯粹的函数来定义无状态的组件(stateless function),这种组件没有状 态,没有生命周期,只是简单的接受 props 渲染生成 DOM 结构。无状态组件非常 简单,开销很低,如果可能的话尽量使用无状态组件。比如使用箭头函数定义: const HelloMessage = (props) => <div> Hello {props.name}</div>; render(<HelloMessage name="John" />, mountNode); 因为无状态组件只是函数,所以它没有实例返回,这点在想用 refs 获取无状态组件 的时候要注意,参见DOM 操作。 组件 19

20.React 入门教程 组件生命周期 一般来说,一个组件类由 extends Component 创建,并且提供一个 render 方法以及其他可选的生命周期函数、组件相关的事件或方法来定义。 一个简单的例子: import React, { Component } from 'react'; import { render } from 'react-dom'; class LikeButton extends Component { getInitialState() { return { liked: false }; } handleClick(e) { this.setState({ liked: !this.state.liked }); } render() { const text = this.state.liked ? 'like' : 'haven\'t liked'; return ( <p onClick={this.handleClick.bind(this)}> You {text} this. Click to toggle. </p> ); } } render( <LikeButton />, document.getElementById('example') ); getInitialState 初始化 this.state 的值,只在组件装载之前调用一次。 组件生命周期 20

21.React 入门教程 如果是使用 ES6 的语法,你也可以在构造函数中初始化状态,比如: class Counter extends Component { constructor(props) { super(props); this.state = { count: props.initialCount }; } render() { // ... } } getDefaultProps 只在组件创建时调用一次并缓存返回的对象(即在 React.createClass 之后就 会调用)。 因为这个方法在实例初始化之前调用,所以在这个方法里面不能依赖 this 获取 到这个组件的实例。 在组件装载之后,这个方法缓存的结果会用来保证访问 this.props 的属性时, 当这个属性没有在父组件中传入(在这个组件的 JSX 属性里设置),也总是有值 的。 如果是使用 ES6 语法,可以直接定义 defaultProps 这个类属性来替代,这样 能更直观的知道 default props 是预先定义好的对象值: Counter.defaultProps = { initialCount: 0 }; render 必须 组装生成这个组件的 HTML 结构(使用原生 HTML 标签或者子组件),也可以返 回 null 或者 false ,这时候 ReactDOM.findDOMNode(this) 会返回 null 。 组件生命周期 21

22.React 入门教程 生命周期函数 装载组件触发 componentWillMount 只会在装载之前调用一次,在 render 之前调用,你可以在这个方法里面调用 setState 改变状态,并且不会导致额外调用一次 render componentDidMount 只会在装载完成之后调用一次,在 render 之后调用,从这里开始可以通过 ReactDOM.findDOMNode(this) 获取到组件的 DOM 节点。 更新组件触发 这些方法不会在首次 render 组件的周期调用 componentWillReceiveProps shouldComponentUpdate componentWillUpdate componentDidUpdate 卸载组件触发 componentWillUnmount 更多关于组件相关的方法说明,参见: Component Specs Component Lifecycle Component API 组件生命周期 22

23.React 入门教程 事件处理 一个简单的例子: import React, { Component } from 'react'; import { render } from 'react-dom'; class LikeButton extends Component { getInitialState() { return { liked: false }; } handleClick(e) { this.setState({ liked: !this.state.liked }); } render() { const text = this.state.liked ? 'like' : 'haven\'t liked'; return ( <p onClick={this.handleClick.bind(this)}> You {text} this. Click to toggle. </p> ); } } render( <LikeButton />, document.getElementById('example') ); 可以看到 React 里面绑定事件的方式和在 HTML 中绑定事件类似,使用驼峰式命 名指定要绑定的 onClick 属性为组件定义的一个方法 {this.handleClick.bind(this)} 。 注意要显式调用 bind(this) 将事件函数上下文绑定要组件实例上,这也是 React 推崇的原则:没有黑科技,尽量使用显式的容易理解的 JavaScript 代码。 事件处理 23

24.React 入门教程 “合成事件”和“原生事件” React 实现了一个“合成事件”层(synthetic event system),这个事件模型保证了 和 W3C 标准保持一致,所以不用担心有什么诡异的用法,并且这个事件层消除了 IE 与 W3C 标准实现之间的兼容问题。 “合成事件”还提供了额外的好处: 事件委托 “合成事件”会以事件委托(event delegation)的方式绑定到组件最上层,并且在组 件卸载(unmount)的时候自动销毁绑定的事件。 什么是“原生事件”? 比如你在 componentDidMount 方法里面通过 addEventListener 绑定的事件 就是浏览器原生事件。 使用原生事件的时候注意在 componentWillUnmount 解除绑定 removeEventListener 。 所有通过 JSX 这种方式绑定的事件都是绑定到“合成事件”,除非你有特别的理由, 建议总是用 React 的方式处理事件。 Tips 关于这两种事件绑定的使用,这里有必要分享一些额外的人生经验 如果混用“合成事件”和“原生事件”,比如一种常见的场景是用原生事件在 document 上绑定,然后在组件里面绑定的合成事件想要通过 e.stopPropagation() 来阻 止事件冒泡到 document,这时候是行不通的,参见 Event delegation,因为 e.stopPropagation 是内部“合成事件” 层面的,解决方法是要用 e.nativeEvent.stopImmediatePropagation() ”合成事件“ 的 event 对象只在当前 event loop 有效,比如你想在事件里面调用 一个 promise,在 resolve 之后去拿 event 对象会拿不到(并且没有错误抛 出): handleClick(e) { promise.then(() => doSomethingWith(e)); } 事件处理 24

25.React 入门教程 详情见 Event pooling 说明。 参数传递 给事件处理函数传递额外参数的方式: bind(this, arg1, arg2, ...) render: function() { return <p onClick={this.handleClick.bind(this, 'extra param')}> }, handleClick: function(param, event) { // handle click } React 支持的事件列表 事件处理 25

26.React 入门教程 DOM 操作 大部分情况下你不需要通过查询 DOM 元素去更新组件的 UI,你只要关注设置组件 的状态( setState )。但是可能在某些情况下你确实需要直接操作 DOM。 首先我们要了解 ReactDOM.render 组件返回的是什么? 它会返回对组件的引用也就是组件实例(对于无状态状态组件来说返回 null),注 意 JSX 返回的不是组件实例,它只是一个 ReactElement 对象(还记得我们用 纯 JS 来构建 JSX 的方式吗),比如这种: // A ReactElement const myComponent = <MyComponent /> // render const myComponentInstance = ReactDOM.render(myComponent, mountNode); myComponentInstance.doSomething(); findDOMNode() 当组件加载到页面上之后(mounted),你都可以通过 react-dom 提供的 findDOMNode() 方法拿到组件对应的 DOM 元素。 import { findDOMNode } from 'react-dom'; // Inside Component class componentDidMound() { const el = findDOMNode(this); } findDOMNode() 不能用在无状态组件上。 Refs DOM 操作 26

27.React 入门教程 另外一种方式就是通过在要引用的 DOM 元素上面设置一个 ref 属性指定一个名 称,然后通过 this.refs.name 来访问对应的 DOM 元素。 比如有一种情况是必须直接操作 DOM 来实现的,你希望一个 <input/> 元素在 你清空它的值时 focus,你没法仅仅靠 state 来实现这个功能。 class App extends Component { constructor() { return { userInput: '' }; } handleChange(e) { this.setState({ userInput: e.target.value }); } clearAndFocusInput() { this.setState({ userInput: '' }, () => { this.refs.theInput.focus(); }); } render() { return ( <div> <div onClick={this.clearAndFocusInput.bind(this)}> Click to Focus and Reset </div> <input ref="theInput" value={this.state.userInput} onChange={this.handleChange.bind(this)} /> </div> ); } } DOM 操作 27

28.React 入门教程 如果 ref 是设置在原生 HTML 元素上,它拿到的就是 DOM 元素,如果设置在自 定义组件上,它拿到的就是组件实例,这时候就需要通过 findDOMNode 来拿到 组件的 DOM 元素。 因为无状态组件没有实例,所以 ref 不能设置在无状态组件上,一般来说这没什么 问题,因为无状态组件没有实例方法,不需要 ref 去拿实例调用相关的方法,但是 如果想要拿无状态组件的 DOM 元素的时候,就需要用一个状态组件封装一层,然 后通过 ref 和 findDOMNode 去获取。 总结 你可以使用 ref 到的组件定义的任何公共方法,比如 this.refs.myTypeahead.reset() Refs 是访问到组件内部 DOM 节点唯一可靠的方法 Refs 会自动销毁对子组件的引用(当子组件删除时) 注意事项 不要在 render 或者 render 之前访问 refs 不要滥用 refs ,比如只是用它来按照传统的方式操作界面 UI:找到 DOM - > 更新 DOM DOM 操作 28

29.React 入门教程 组合组件 使用组件的目的就是通过构建模块化的组件,相互组合组件最后组装成一个复杂的 应用。 在 React 组件中要包含其他组件作为子组件,只需要把组件当作一个 DOM 元素引 入就可以了。 一个例子:一个显示用户头像的组件 Avatar 包含两个子组件 ProfilePic 显 示用户头像和 ProfileLink 显示用户链接: 组合组件 29