第一篇:纯 React&纯 Redux

React

第一章 技术简介

React

Q:React 是什么?

A:React 是一个声明式、高效、灵活的、创建用户界面的 JavaScript 库

Redux

Q:Rudex 是什么?

A:Redux 是一个 JavaScript 状态容器,提供科预测的状态管理

Redux 三大原则:单一数据源、state 只读、使用纯函数来执行修改

单一数据源:在整个应用的 state 被存储在一颗对象树中,并且这个对象树只存在于唯一一个 store 中。state 指的是数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
console.log(store.getState())

/* Prints
{
visibilityFilter: 'SHOW_ALL',
todos: [
{
text: 'Consider using Redux',
completed: true,
},
{
text: 'Keep all state in a single tree',
completed: false
}
]
}
*/

state 只读:并不代表我们不能修改 state 。只读指的是不想允许直接对 state 这个变量重写赋值,但可以通过 action 和 reducer 返回一个新的 state。

1
2
3
4
5
6
7
8
9
store.dispatch({
type: 'COMPLETE_TODO',
index: 1
});

store.dispatch({
type: 'SET_VISIBILITY_FILTER',
filter: 'SHOW_COMPLETED'
});

使用纯函数来执行修改:更新 state 的 reducer 只是一些纯函数,接收先前的 state 和 action, 并返回新的 state。

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
49
/**
visibilityFilter 是一个 reducer
接收 state 作为第一个参数,并初始化为'SHOW_ALL'
第二个参数为 action
使用 switch 来判断 action.type
并根据当前的 action.type
return 新的 state
**/

function visibilityFilter(state = 'SHOW_ALL', action) {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter
default:
return state
}
}

function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
text: action.text,
completed: false
}
]
case 'COMPLETE_TODO':
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: true
})
}
return todo
})
default:
return state
}
}

import { combineReducers, createStore } from 'redux'
/**
combineReducers 将两个 reducer 绑定成新的 reducer
createStore 把 reducer 包装生成 store
**/
let reducer = combineReducers({ visibilityFilter, todos })
let store = createStore(reducer)

Q:为什么使用 Redux

A:可预测、便于组织管理代码、支持 Universal 渲染、优秀的拓展能力、容易测试、开发工具、社区和生态系统

为什么使用 Redux

可预测:Redux 是一个数据源,想要修改它只能发起 action。reducer 又是纯函数,也就是说输入永远会得到相同的输出。这一切让程序运行变得可控、可预测。

便于组织管理代码:严格而明确的程序结构使得代码更容易组织和管理。

支持 Universal 渲染:单一数据源这个原则可以帮助解决 Universal 渲染中的数据传输问题,服务器渲染后只需给客户端传递一个变量即可,这个变量就存储 state 的对象树

优秀的拓展能力:很多的中间件拓展

容易测试:写单一功能的、没有外界以来的小型函数

开发工具:可以通过使用 redux 开发工具实现追踪、回退和重放程序中的 action 和 state

Node&Universal 渲染

React&Redux 是实现 Universal 渲染的理想技术组合

Babel

Babel 是一个 JavaScript 编译器

Babel 工具

  • Babel 内建工具:Babel CLI、Require Hook
  • 各种构建系统:Webpack、Gulp、Grunt、RequireJs 等
  • 测试框架:Jasmin、Karma、Mocha 等
  • 语言 API:C#/.NET、Node、Ruby
  • 模板引擎:Jade
  • 编辑器:WebStrom
  • 调试器:Node Inspector

Webpack

前端资源模块化管理和打包工具。通过加载器 (loader) 的转换,任何形式的资源都可以视为模块

  • 编译、加载使用 ES2015 和 JSX 语法的模块
  • 实现开发服务器和热替换
  • 加载图片文件
  • 加载字体文件
  • 加载样式文件
  • 加载 Json 文件
  • 使用同构工具实现同构渲染
  • 压缩代码
  • 哈希命名

总结

第二章 在 Node.js 中运行 React

编写 React 组件

通常我们需要写一个继承 (extends) 自 React.Component 的类,并在 render() 中返回你要展示的视图

1
2
3
4
5
6
7
import React from 'react';

export default class App extends React.Component{
render(){
return <h1>hello world</h1>;
}
}

当如果这个组件只有一个 render 方法,可以写成下面的无状态组件,这也是 Aribnb 编码规范推荐的写法

1
2
3
4
5
6
//src/App.js
import React from 'react';

export default function App() {
return <h1>hello world</h1>;
}

export 就是抛出这个组件,default 表示你能在别的文件中使用 import App form './App' 导入这个组件。如果没有 default 则需要 import { App } form './App' 导入这个组件。

在 Node.js 中渲染组件

src/server.js中引入 (import) 上面的组件,然后将其渲染成一个 HTML 字符串打印出来。在 Node.js 中我们使用renderToString()方法将组件渲染成字符串

1
2
3
4
5
6
7
8
//src/server.js
import React from 'react';
import { renderToString } from 'react-dom/server';
import App from './App';

const appHTML = renderToString(<App />);

console.log(appHTML);

使用 Babel 编译运行 Node.js 程序

使用 Require Hook 给每一个 require 方法上加上一个钩子,每次使用 require 加载 JavaScript 文件事,先使用 Babel 进行编译

注意:Require Hook 优势是将编译和运行合二为一,造成劣势是性能消耗大,所以只在测试环境中使用

1.Require Hook 安装:npm i babel-register --save-dev

  1. 使用:在入口文件顶部添加require("babel-register")
1
2
3
//index.js
require('babel-register');
require('./src/server.js');
  1. 安装 ES2015 和 React 的预设
1
npm i --save-dev babel-preset-es2015 babel-preset-react
  1. 添加配置文件./babelrc,激活 ES2015 和 React 预设
1
2
3
4
//.babelrc
{
"presets": ["react", "es2015"]
}

运行

1
2
3
4
5
6
7
8
9
➜  2 npm i
npm notice created a lockfile as package-lock.json. You should commit this file.
added 119 packages in 24.763s
➜ 2 npm start

> 02-react-node@1.0.0 start /home/mike/文档/note/R&R/rar/2
> node index

<h1 data-reactroot="" data-reactid="1" data-react-checksum="109777506">hello world</h1>

总结

使用模块

react.js [必需]

React 是用来构建用户界面的 js 库,属于 view 层。
它有两大特点:1,单向数据绑定;2,虚拟 DOM
安装:npm install --save react

babel-preset-react [必需]

react 转码规则。为所有 react 插件所设置的 babel 预设。有了它,才能识别转译 jsx 语法等。
安装:npm install --save-dev babel-preset-react

babel-preset-latest [必需]

es2015,es2016,es2017 转码规则。为所有 es6 插件所设置的 babel 预设,
有了它,诸如,es6 的箭头函数,类,等等语法特性才能向 es5 转换。
安装:npm install --save-dev babel-preset-latest
而这里使用的是babel-preset-es2015
具体的 babel-preset-latest配置看 minooo 的配置

react-dom.js [必需]

react.js 主要用来创建元素和组件,当你想在 html 中渲染你的组件的时候,
你还得需要 react-dom.js。同时,react-dom.js 依赖于 react.js。
安装:npm install --save react-dom


第三章 在浏览器中运行 React

例子:

1
2
3
npm i
npm run build
npm start

组件复用

把第二章的 App.js 复制过来

在浏览器中渲染 React

1
2
3
4
5
6
//src/client.js
import React from 'react';
import { render } from 'react-dom';
import App from './App';

render((<App />), document.querySelector('#app'));

以上代码将<App />组件渲染到 ID 为 app 的标签中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<title>React Demo</title>
</head>

<body>
<div id="app"></div>
<script src="static/dist/main.js"></script>
</body>

</html>

使用 Webpack 打包编译

  1. 安装 Webpack 和 babel-loader
1
npm i --save-dev webpack babel-loader babel-core
  1. 添加 webpack.config.js 文件指定打包编译的配置信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports = {
entry: './src/client',
//入口文件位置
output: {
path: __dirname + '/static/dist',
//文件夹路径
filename: 'main.js'
//文件名
},
//出口文件位置
module: {
loaders: [{ test: /\.js$/, exclude: /node_modules/, loaders: ['babel'] }]
}
//babel-loader 配置信息
//除了‘/node_modules/’文件夹,其他地方的`*.js`文件都用 babel-loader 编译
};

  1. 同样需要安装 ES2015 和 React 的预设
1
npm i --save-dev babel-preset-es2015 babel-preset-react
  1. 添加配置文件./babelrc,激活 ES2015 和 React 预设
1
2
3
4
//.babelrc
{
"presets": ["react", "es2015"]
}

总结

react.js [必需]

React 是用来构建用户界面的 js 库,属于 view 层。
它有两大特点:1,单向数据绑定;2,虚拟 DOM
安装:npm install --save react


react-dom.js [必需]

react.js 主要用来创建元素和组件,当你想在 html 中渲染你的组件的时候,
你还得需要 react-dom.js。同时,react-dom.js 依赖于 react.js。
安装:npm install --save react-dom


webpack [必需]

于人而言,尤其是当开发大型项目时,每个包每个模块每个静态资源都应尽可能的条理清晰的罗列出来,
这样方便我们开发;于机器而言,就不需要这么“条理清晰”了,此时应最大限度的压缩优化这些资源,
如何把这些资源模块“杂糅”在一起,这就是 webpack 要做的。
安装:npm install --save-dev webpack
备注:webpack 2.0 即将发布
webpack 最基本的启动 webpack 命令
webpack -w 提供 watch 方法,实时进行打包更新
webpack -p 压缩混淆脚本,这个非常非常重要!
webpack -d 生成 map 映射文件,告知哪些模块被最终打包到哪里了,方便调试
webpack –progress 显示打包进程,百分比显示
webpack –config XXX.js //使用另一份配置文件(比如 webpack.config2.js)来打包 webpack –colors 输出结果带彩色,比如:会用红色显示耗时较长的步骤
webpack –profile 输出性能数据,可以看到每一步的耗时
webpack –display-error-details 方便出错时能查阅更详尽的信息(比如 webpack 寻找模块的过程),从而更好定位到问题。
webpack –display-modules 默认情况下 node_modules 下的模块会被隐藏,加上这个参数可以显示这些被隐藏的模块
webpack 入门配置


babel-preset-react [必需]

react 转码规则。为所有 react 插件所设置的 babel 预设。有了它,才能识别转译 jsx 语法等。
安装:npm install --save-dev babel-preset-react


babel-preset-latest [必需]

es2015,es2016,es2017 转码规则。为所有 es6 插件所设置的 babel 预设,
有了它,诸如,es6 的箭头函数,类,等等语法特性才能向 es5 转换。
安装:npm install --save-dev babel-preset-latest
而这里使用的是babel-preset-es2015
具体的 babel-preset-latest配置看 minooo 的配置

第四章 开发服务器和热替换

例子

1
2
npm i
npm start

之前直接同 webpack 打包,每次修改后都需要先运行一次npm run build,再手动刷新浏览器,效率实在是太低了。懒癌发作,使用开发服务器(webpack-dev-server)和热替换(hot-reloadeing)技术解决这个问题

配置 Babel

配置 Babel 时期支持热替换的最快捷方式是使用react-hmre

1
2
3
4
5
6
7
8
9
10
//.babel
{
"presets": ["es2015", "react"],
"env": {
"development": {
"presets": ["react-hmre"]
}
}
}
//限制只在开发时候用 react-hmre

react-hmre 功能

这个预设的第一个功能是热替换 React 模块,还可以捕获错误,并将包含错误对战信息的红色警告页面输出到浏览器。

注意:如果将无状态组件放在组件顶层,热替换将会出错,所以现在的 App 组件是类组件。

配置 Webpack

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
var path = require('path');
//引入 path 模块
var webpack = require('webpack');
//引入 webpack 模块
module.exports = {
devtool: 'cheap-module-eval-source-map',
//devtool 是生成源码映射(source map),方便调试。开发时点击控制台警报可以调到源码位置而不是编译后的代码
entry: [
//入口文件
'webpack-hot-middleware/client',
//引入热替换的中间件。原理是:先连接上服务器,等接受需要重新编译的代码的通知,一旦代码变动吗,就会收到通知进而更新客户端代码
'./index.js'
],
output: {
//输出文件
path: path.join(__dirname, 'dist'),
//输出文件夹
filename: 'bundle.js',
//输出文件名
publicPath: '/static/'
//公共路径,所有资源的前缀,这个路径是虚拟的,无法在硬盘中找到,被编译后的脚本被保存在内存中
},
plugins: [
//插件列表
new webpack.optimize.OccurrenceOrderPlugin(),
//用于给经常使用的模块分配最小长度的 ID
new webpack.HotModuleReplacementPlugin()
//用于热替换
],
module: {
loaders: [
{
test: /\.js$/,//后缀类型
loaders: ['babel'],//加载器类型
exclude: /node_modules/,//排除文件夹
include: __dirname
}
]
}
};

配置 Express 服务器

在 Express 服务器中添加 webpackDevMiddleware 和 webpackHot-Middleware 两个中间件就能完成开发服务器和热替换的配置工作。

webpackDevMiddleware:将 webpack 的打包功能与 Express 服务器的资源服务功能合二为一。Express 通过它打包并把资源读取到内存中,它还可以监视代码变动,就会停止提供久资源,等编译完成后继续提供新资源

webpackHot-Middleware:webpackDevMiddleware 中间件无法完成热替换,也不能在更新后保留程序状态。为了实现热替换,还应该在 webpackDevMiddleware 基础上加上 webpackHot-Middleware

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
//server.js
/* eslint-disable */
var webpack = require('webpack');
var webpackDevMiddleware = require('webpack-dev-middleware');
var webpackHotMiddleware = require('webpack-hot-middleware');
var config = require('./webpack.config');

var app = new (require('express'))();
var port = 3000;

var compiler = webpack(config);
app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath }));
app.use(webpackHotMiddleware(compiler));

app.get("/", function(req, res) {
res.sendFile(__dirname + '/index.html')
});

app.listen(port, function(error) {
if (error) {
console.error(error)
} else {
console.info("==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.", port, port)
}
});

总结

####babel-core [必需]

Babel 是一个转换编译器,它能将 ES6 转换成可以在浏览器中运行的代码。
作为下一代 javascript 语言标准,请拥抱 ES6(ES2015) 吧!babel-core 是 Babel 编译器的核心。
安装:npm install --save-dev babel-core


####babel-loader [必需]

loader 用于转换应用程序的资源文件,他们是运行在 nodejs 下的函数,
使用参数来获取一个资源的来源并且返回一个新的来源针对 webpack 的 babel 加载器。
babel-loader 就是告诉 webpack 去加载我们写的使用了 es6 语法的 js 文件。
安装:npm install --save-dev babel-loader


babel-preset-react [必需]

react 转码规则。为所有 react 插件所设置的 babel 预设。有了它,才能识别转译 jsx 语法等。
安装:npm install --save-dev babel-preset-react


babel-preset-latest [必需]

es2015,es2016,es2017 转码规则。为所有 es6 插件所设置的 babel 预设,
有了它,诸如,es6 的箭头函数,类,等等语法特性才能向 es5 转换。
安装:npm install --save-dev babel-preset-latest
而这里使用的是babel-preset-es2015
具体的 babel-preset-latest配置看 minooo 的配置


react.js [必需]

React 是用来构建用户界面的 js 库,属于 view 层。
它有两大特点:1,单向数据绑定;2,虚拟 DOM
安装:npm install --save react


react-dom.js [必需]

react.js 主要用来创建元素和组件,当你想在 html 中渲染你的组件的时候,
你还得需要 react-dom.js。同时,react-dom.js 依赖于 react.js。
安装:npm install --save react-dom


webpack [必需]

于人而言,尤其是当开发大型项目时,每个包每个模块每个静态资源都应尽可能的条理清晰的罗列出来,
这样方便我们开发;于机器而言,就不需要这么“条理清晰”了,此时应最大限度的压缩优化这些资源,
如何把这些资源模块“杂糅”在一起,这就是 webpack 要做的。
安装:npm install --save-dev webpack
备注:webpack 2.0 即将发布
webpack 最基本的启动 webpack 命令
webpack -w 提供 watch 方法,实时进行打包更新
webpack -p 压缩混淆脚本,这个非常非常重要!
webpack -d 生成 map 映射文件,告知哪些模块被最终打包到哪里了,方便调试
webpack –progress 显示打包进程,百分比显示
webpack –config XXX.js //使用另一份配置文件(比如 webpack.config2.js)来打包 webpack –colors 输出结果带彩色,比如:会用红色显示耗时较长的步骤
webpack –profile 输出性能数据,可以看到每一步的耗时
webpack –display-error-details 方便出错时能查阅更详尽的信息(比如 webpack 寻找模块的过程),从而更好定位到问题。
webpack –display-modules 默认情况下 node_modules 下的模块会被隐藏,加上这个参数可以显示这些被隐藏的模块
webpack 入门配置


webpack-dev-middleware [开发需要]

它是一个用来组织包装 webpack 使其变成中间件的容器。(中间件的用途就是在输入和输出的过程中加工的一种手段)
webpack 本身只负责打包编译,webpack-dev-server 是协助我们开发的服务器,这个服务器底层是靠 express 操作的。
我们的页面如何在这个服务器上更新呢,首先是取得 webpack 打包好的资源,这就需要在请求响应的过程中通过
express 的中间件取得资料, 而方法就是通过 webpack-dev-middleware 来实现。
这个中间件只在开发环境中使用,切忌用在生产环境中。
安装:npm install --save-dev webpack-dev-middleware

这个中间件有两点好处:

  1. 直接在内存中操作文件,而非磁盘中。这样处理速度更快。
  2. 在监视(watch)模式下,如果文件改变,中间件立即停止提供之前的 bundle,并且会延迟
    请求回应,直到新的编译完成,如此一来,文件修改后,你可以直接刷新页面,而不用等待编译。

webpack-hot-middleware [开发需要]

webpack-dev-middleware + webpack-hot-middleware 即可让我们用 express
定制一个有热替换功能的 webpack 开发服务器。
安装:npm install --save-dev webpack-hot-middleware

babel-preset-react-hmre

这个预设的第一个功能是热替换 React 模块,还可以捕获错误,并将包含错误对战信息的红色警告页面输出到浏览器。

1
>npm install babel-preset-react-hmre --save-dev

express [开发需要]

基于 Node.js 平台,快速、开放、极简的 web 开发框架。
在这里用于配置开发服务器。
安装:npm install --save-dev express


第五章 React 的创新语法:JSX

JSX 简介

jsx 是一个看起来像 XML 的 JavaScript 语法扩展,这种语法允许你在 JavaScript 中写可嵌套的闭合标签,JSX 可以让组件的结构和组件之间的关系看上去更加清晰。

JSX 语法

  • 类似 HTML:可以嵌套,可以自定义属性
  • JavaScript 表达式
  • 样式:内联样式不是字符串,而是对象
  • 注释:标签字节点内的注释应该写在 {}
  • 数组:数组会自动展开。注意,数组中每一项元素需要添加 key 属性

JSX 常用语法

类似 HTML

JSX 与 HTML 非常相似,可以嵌套多个 HTML 标签,也可以使用大部分符合 HTML 规范的属性,比如 style。如果往 HTML 中传入了 HTML 规范里没有的属性, React 不会显示它们,但是可以通过加上 data- 前缀的办法自定义属性。

1
2
3
4
5
6
7
8
9
//src/App.js
function Demo1(){
return (
<li>
<h3>类似 HTML</h3>
<p data-attribute="demo1">可以嵌套,可以自定义属性</p>
</li>
);
}

注意:因为 JSX 终究还是 JavaScript ,而 classfor 又是 JavaScript 的保留字,所有尽管 JSX 中的 HTML 标签大多数和 HTML 规范的一致,但是 classfor 这两个属性在 JSX 中需要写成 classNamehtmlFor

JavaScript 表达式

JSX 允许在闭合标签中使用 JavaScript 表达式,但是要被{}包裹。 JavaScript 表达式要求必须有返回值,因此无法直接使用 if else 语句,但是可以使用三元操作表达式以及 ||&& 这样的比较运算符来书写。如果确实需要使用 if else 语句,可以将其卸载函数中,然后在 {} 中调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//src/App.js
function Demo2(){
const name = 'JSX';
const func = () =>{
let result = 'hello'
if(name){
result += 'world';
} else {
result += 'world'
}
return result
};
return(
<li>
<h3>JavaScript 表达式</h3>
<p>hello {name || 'world'}</p>
<p className={name ? 'class-a' : 'class-b'}>hello {name && 'world'}</p>
<p>{func()}</p>
</li>
);
}

样式

总体来说,样式分为内联式、内嵌式、链接式等,这里要讲解的是内联样式的写法。与 HTML 相似,JSX 中的内联样式也可以通过 style 属性来定义,但属性值不能是字符串而必须是对象,而且要注意对象的属性名需要使用驼峰命名法,例如需要把 font-size 写成 fontSize

1
2
3
4
5
6
7
8
9
//src/App.js
function Demo3(){
return (
<li>
<h3>样式</h3>
<p style={{ color:'red',fontSize:'14px'}}>内联样式不是字符串,而是对象</p>
</li>
);
}

注释

在 JSX 中添加注释非常简单,只需要注意将标签字节点内的注释写在 {} 中就可以了。

1
2
3
4
5
6
7
8
9
10
//src/App.js
function Demo4() {
return (
<li>
<h3>注释</h3>
{/* 注释。..*/}
<p>标签字节点内注释应该写在打括号中</p>
</li>
);
}

数组

JSX 中的数组会自动展开所有成员。但是需要注意,如果数组或迭代器中的每一项都是 HTML 标签或组件,那么它们必须要拥有唯一的 key 属性。这样做是为 React 的 DIFF 算法服务的,React 会通过唯一的 key 属性实现最高效的 DOM 更新。

1
2
3
4
5
6
7
8
//src/App.js
function Demo5(){
const arr= [
<h3 key={0}>数组</h3>,
<p key={1}>数组会自动展开。注意,数组中每一项元素需要添加 key 属性</p>
];
return (<li>{arr}</li>);
}

HTML 标签 vs. React 组件

React 可以渲染 HTML 标签或 React 组件。HTML 使用小写字母的标签名,而 React 组件的标签名首字母需要大写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//src/App.js
export default class App extends Component{
render(){
return(
<div>
<h2>JSX 语法</h2>
<ul>
<Demo1 />
<Demo2 />
<Demo3 />
<Demo4 />
<Demo5 />
</ul>
...
</div>
)
}
}

第六章 React 的数据载体:state、props 与 context

State:应该成为局部状态或内部状态。

props:用于在组件间传递数据,仅支持逐层传递

context:用于在组件间传递数据,能够跨级传递

State

React 组件可以通过在构造函数中初始化内部状态,可以通过this.setState方法更新内部状态,还可以使用this.state获取内部状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export default class Counter extends Component{
constructor(){
super()
this.state = { value: 0 }
}
render(){
return(
<div>
<button onClick={()=>this.setState({value:this.state.value+1})}>
INCREMENT
</button>
Counter 组件的内部状态
<pre>{JSON.stringify(this.state,null,2)}</pre>
</div>
)
}
}

首先在构造函数中初始化内部状态,然后给 button 的点击事件注册了一个更新内部状态的方法,最后将内部状态序列化显示在 pre 标签中。

随着无状态函数(无状态函数没有内部状态)的提出和 Redux 的使用,内部状态的使用正在逐渐减少。但是内部状态在非全局的数据管理更新中仍扮演着重要的角色。

Props

props 就是属性的意思

使用 props

向一个组件传递 props 的方法是将数据卸载组件标签的属性中

1
2
//src/Counter
<Counter value={this.state.value}/>

组件怎么获取传递过来的 props 呢?在无状态函数编写的组件中获取 props 非常简单。只需要将 props 作为参数传入组件即可

1
2
3
4
//src/Counter
function Content(props){
return <p>Content 组件的 props.value:{props.value}</p>
}

使用类编写的组件中,需要通过 this.props 获取 props 。this 是组件实例。

验证 Props

验证 props 需要用到 React.PropTypes,它提供很多验证器(validator)来验证传入的数据是否合法。当想 props 传入非法数据时,控制台会抛出警告。

PropTypes 提供的验证器

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
//1.JavaScript 基本数据类型
React.PropTyes.array//数组
React.PropTyes.bool//布尔值
React.PropTyes.func//方法
React.PropTyes.number//数字
React.PropTyes.object//对象
React.PropTyes.string//字符串
//2. 可以渲染为子节点的对象
React.PropTyes.node//节点
React.PropTyes.element//React 组件
React.PropTyes.instanceOf(Message)//指定类的实例
React.PropTyes.oneOf(['News','Photos'])//只接受指定的值
React.PropTyes.oneType([
React.PropTyes.string,
React.PropTyes.number,
React.PropTyes.instanceOF(Messgae)
])//多个对象中的一个
React.PropTyes.arrayOf(React.PropTyes.number)//指定类型组成的数组 [1,2,3]
React.PropTyes.objectOf(React.PropTyes.number)//指定类型的属性构成的对象
React.PropTyes.shape({
color:React.PropTyes.string,
fontSize:React.PropTyes.number
})//符合指定格式的对象
React.PropTyes.func.isRequeired//任意类型加上 isRequeired 使其不可为空
//11 自定义验证器,如果验证失败需要返回一个 Error 对象,不用直接 console.warn 或抛出异常,因为这样的话 oneOfType 会失效
function(props,propName,componentName){
if(!/matchme/.test(propName)){
return new Error('Validation failed!')
}
}
1
2
3
4
5
6
//src/Counter
import React,{Component} from 'React'
...
Content.propTyoes={
value:PropTypes.number.isRequired
};

组合使用 state 与 props

这里用了一个 Counter 组件更新 state.value,然后将更新的 state.value 通过 props 传递给 Content 组件,最后 Content 组件在每次更新时都渲染出新接受到的 props.value

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
//src/Counter
import React,{Component,PropTyoes} from 'React'

function Content(props){
return <p>Content 组件的 props.value:{props.value}</p>;
}

Content.propTyoes={
value:PropTypes.number.isRequired
};

export default class Counter extend Component{
constructor(){
super();
this.state={
value:0
}
}
render(){
return(
<div>
<button onClick={()=>this.setState({value:this.state.value+1})}>
INCREMENT
</button>
Counter 组件的内部状态;
<pre>{JSON.stringify(this.state,null,2)}</pre>
<Content value={this.state.value}/>
</div>
)
}
}

Context

context 在 React 中并不常用到,后面的 react-redux 会用到 context。现在先拿出之前的 props 和 context 做比较。

使用 props 传递数据

编写三个组件,分别是 Button 、Message 和 MessageList,在 MessageList 中定义一个 color 变量,通过 props 将 color 传递给 Button 组件

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
49
50
51
52
53
54
55
//src/Messagelist1
import React, { PropTypes } from 'react';
/*
无状态函数组件 Button
接收 props
*/
function Button(props) {
return (
<button style={{ background: props.color }}>
{props.children}{/*这里的 props.children 其实是父组件插在组件标签中的东西*/}
</button>
);
}
//验证 props
Button.propTypes = {
color: PropTypes.string.isRequired,
children: PropTypes.string.isRequired
};
/*
无状态函数组件,接收 props,
获取到 props.text,
并把 props.color 传递给 Button 组件
*/
function Message(props) {
return (
<li>
{props.text} <Button color={props.color}>Delete</Button>
</li>
);
}
//验证 props
Message.propTypes = {
text: PropTypes.string.isRequired,
color: PropTypes.string.isRequired
};
//父组件
function MessageList() {
const color = 'gray';
const messages = [
{ text: 'Hello React' },
{ text: 'Hello Redux' }
];
const children = messages.map((message, key) =>
<Message key={key} text={message.text} color={color}/>
);//这里的 key 虽然没有用到,不过遍历出来的组件需要 Key 来重新排列
return (
<div>
<p>通过 props 将 color 逐层传递给里面的 Button 组件</p>
<ul>
{children}
</ul>
</div>);
}

export default MessageList;

使用 context 传递数据

要使用 context 传递数据,需要两个步骤

  1. 将要传递的数据放在消息列表组件(数据发起位置)的 context 中
  2. 在按钮组件(接收数据的子组件位置)中声明 contextTypes,就可以通过 context 传递数据
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
49
50
51
52
53
54
55
56
57
58
59
60
//src/Messagelist2
import React, { Component, PropTypes } from 'react';

function Button(props, context) {
return (
<button style={{ background: context.color }}>{/*通过 context.color 拿到数据*/}
{props.children}
</button>
);
}

Button.propTypes = {
children: PropTypes.string.isRequired
};

Button.contextTypes = {
color: PropTypes.string.isRequired
};//如果没有 contextTypes,context 将会是空值

function Message(props) {
return (
<li>
{props.text} <Button>Delete</Button>{/*和之前不一样,这里没把 props 传递给 Button*/}
</li>
);
}

Message.propTypes = {
text: PropTypes.string.isRequired
};

class MessageList extends Component {
getChildContext() {
return { color: 'gray' };
}//和之前不同是多了这个函数,fuan 返回值是要传递给 Button 的 color 变量

render() {
const messages = [
{ text: 'Hello React' },
{ text: 'Hello Redux' }
];
const children = messages.map((message, key) =>
<Message key={key} text={message.text}/>
);//这次没有传递 color
return (
<div>
<p>通过 context 将 color 跨级传递给里面的 Button 组件</p>
<ul>
{children}
</ul>
</div>
);
}
}

MessageList.childContextTypes = {
color: PropTypes.string.isRequired
};//多了一个这个验证,没有这个是无法在组件中使用 getChildContext()

export default MessageList;

第七章 React 的两个对象:ReactElement 与组件实例

ReactElement:就是传说中的“虚拟 DOM”,本质是个不可变的对象

组件实例:React 组件类的实例化对象,它通常用来管理内部状态和处理生命周期函数

ReactElement

JSX 中的闭合标签是 ReactElement

注意:只有在 React 中使用 JSX,闭合标签才是 ReactElement,在其他框架(比如 Vue)中使用 JSX 就不是 ReactElement

ReactElement 是什么

ReactElement 是一个不可变的普通对象,它描述了一个组件的实例或一个 DOM 节点。它只包含组件的类型(比如 h1,或者 APP)、属性以及子元素等信息。

ReactElement 不是组件是实例,不能在 ReactElement 中调用 React 组件的任何方法。它只是告诉 React 你想显示什么。

ReactElement 的两种类型

  1. type 属性是一个字符串时,它表示一个 DOM 节点,它的 props 属性对应 DOM 节点的属性
  2. type 属性是一个表示组件的函数或者类,它表示一个组件

React 组件的渲染流程

当 React 遇到表示组件的 ReactElement 时,它会给这个 ReactElement 表示的组件一些 props(有时也包括 context),然后问该组件渲染的 ReactElement 是什么。如果渲染的仍然是表示组件的 ReactElement,那么将会一直吻下去,直到了解所以组件要渲染的 DOM 元素为止,此时,React 就可以使用 react-dom 或者 react-native 这样的渲染模快来执行渲染。

组件实例

大多数情况下,我们无需直接创建组件实例,React 会负责创建它。ReactDOM.reder 返回的就是组件实例。除此之外,组件的 this 也指向组件实例。利用 Refs 可以获取组件实例。

注意:无状态函数是没有实例化对象的,因此无法使用生命周期函数,也没有内部状态。所以当你的组件需要使用生命周期函数或者内部状态,请使用类编写该组件。

组件、ReactElement 与组件实例的区别

组件是一个函数或类,它决定了如何把数据变成视图;ReactElement 只是一个普通的对象,它描述了组件实例或 DOM 节点;组件实例则是组件类的实例化对象。

组件实例的生灭:声明周期函数

  • componentWillMount:在渲染前调用
  • componentDidMount:在渲染后调用
  • componentWillReceiveProps:在组件接收到一个新的 props 时被调用,这个方法第一次渲染时不会被调用
  • shouldComponentUpdate:返回一个布尔值,在组件接收到新的 props 或者 state 时被调用。在初始化时或者使用forceUpdate时不被调用。可以在你确定不需要更新组件时使用。
  • componentWillUpadte:在组件接收到新的 props 或者 state 但还没有 render 时被调用。在初始化时不被调用。
  • componentDidupdate:在组件完成更新后立即调用。在初始化时不被调用
  • componentWillUnmount:在组件从 DOM 中移除的时候立即被调用

React 组件中的 this

那些方法的 this 指向组件实例呢?怎么样才能在自定义的组件方法中获得组件实例?

React 组件的 this 到底是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React from 'react'

const stuffix = '被调用,this 指向'

export default class App extends React.Component{
handler(){
console.log(`handler${suffix}`,this)
}
render(){
console.log(`handler${suffix}`,this)
return(
<div>
<h1 onClick={this.handler}>Hello world</h1>
</div>
)
}
}

结果是,render() 函数中的 this 指向组件实例,而 handler() 函数中的 this 则是一个 null。

JavaScript 函数中的 this

JavaScript 函数中的 this 不是在函数声明的时候而是在函数运行的时候定义的。

1
2
3
4
5
6
7
8
var student={
speak:function(){
console.log(this)
}
}
student.speak()
var studentSpeak = student.speak
studentSpeak()

student.speak() 打印了 student 对象,因为 this 指向 student 对象。而 studentSpeak() 打印了 window,因为 this 指向了 window

关于 this 绑定

React.createClass 可以自动绑定所有的方法,使 this 指向组件的实例化对象。在类组件中,上下文转换的自动权交给了开发者,通常我们在构造函数中绑定方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class App extends React.Component{
constructor(props){
super(props)
this.handler = this.handler.bind(this)
}
handler(){
console.log('handler',this)
}
render(){
console,log('render',this)
return(
<div>
<h1 onClick={this.handler}>Hello world</h1>
</div>
);
}
}

将 this.handler() 绑定上下文为组件实例后, this.handler() 中的 this 将指向组件实例。此处也可以用箭头函数或者在属性中临时绑定,不过 Aribnb 编码规范并不推荐在 JSX 的属性中使用 bind 临时绑定,所以尽量在构造函数中进行绑定或者使用箭头函数。

第八章 初识 Redux

Action、reducer、state、store 是 Redux 中的一些基本概念。

Action

Action 本质是 JavaScript 普通对象,action 内使用一个字符串类型的 type 字段来表示将要执行的动作。除了 type 字段外,action 对象的结构完全由你决定。

发起 ation:

1
2
store.dispatch({type:'INCREMENT'})
store.dispatch({type:'DECREMENT'})

其中{type:’DECREMENT’}和{type:’INCREMENT’}就是 action

Reducer

Reducer 是个形式为 (state,action)=>state 的纯函数,描述了 action 如何把 state 转变成下一个 state。reducer 是一个累加器函数,它的参数是上一个累加值和数组当前元素,然后通过计算得到当前的累加值。在 Redux 中,state 就是那个累加值,action 就是数组当前的元素。

1
2
3
4
5
6
7
8
9
10
function counter(state = 0,action){
switch(action.type){
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}

State 可以是任何类型、数组、对象等等,唯一要点是当 state 变化时需要返回全新的对象,而不是修改传入的参数。

纯函数

纯函数不能访问外部变量,只能接收传入参数。纯函数不能修改参数,因为这样做可能会把一些信息通过传入参数夹带到外界。

还不完整

不能修改参数 state

在 JavaScript 中对象是引用类型,修改了参数 state,变化前后的两个 state 将会指向同一个地址,react-redux 就会认为这是两个相同的 state,因而不会执行渲染。

Store

职能

store 是一个全局对象,作用是将 action 和 reducer 以及 state 联系在一起。

  • 维持应用的 state
  • 提供 getState() 方法获取 state
  • 提供 dispatch(action) 方法更新 state
  • 通过 subscribe(listener) 注册监听器

创建

1
2
3
4
import {createStore} from 'redux'
let store = createStore(counter)
//第一个参数是 reducer
//createStore 可以将初始 state 作为第二个参数传入

获取与监听

1
2
3
4
5
6
7
8
const store = createStore(counter)
let currentValue = store.getState()//获取 state
store.subscribe(()=>{
const previousValue = currentValue
currentValue = store.getState()
console.log('pre state:',previousValue,'next state',currentValue)

})

发起 action

Store 通过 dispatch(action) 方法发起 action,更新 state

1
2
store.dispatch('INCREMENT')
store.dispatch('DECREMENT')

当发起 action 后,就将 action 传进了 store,使用 reducer 纯函数执行更新。修改 state 唯一方法就是 dispatch(action),所有的变化都将进过这条路,我们把中间件放在 dispatch 这条路上。

第九章 Action 创建函数与 Redux Thunk 中间件

Action 创建函数

编写

1
2
3
4
5
6
function increment(){
return {type:'INCREMENT'}
}
function decrement(){
return {type:'DECREMENT'}
}

发起

1
2
3
4
5
6
7
store.dispatch(increment())
store.dispatch(decrement())
//or
const boundIncrement = () => dispatch(increment());
boundIncrement();
const boundDecrement = () => dispatch(decrement());
boundDecrement();

Redux Thunk 中间件

这个中间件能让 action 创建函数先不返回 action,而是返回一个函数。通过函数处理后再 dispatch。

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
//全部的 action 函数
function increment() {
return {
type: 'INCREMENT'
};
}

function decrement() {
return {
type: 'DECREMENT'
};
}

function incrementIfOdd() {
return (dispatch, getState) => {
const value = getState();
if (value % 2 === 0) {
return;
}

dispatch(increment());
};
}

function incrementAsync(delay = 1000) {
return dispatch => {
setTimeout(() => {
dispatch(increment());
}, delay);
};
}

安装激活

1
2
3
import {createStore,applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
const store = createStore(counter, applyMiddleware(thunk));

本笔记是我读 《React 与 Redux 开发实例精解》基础篇的学习笔记,所以笔记中大量摘抄了原书的内容,笔记中的代码来源于书作者的开源项目 react-redux-book。往后我还会在博客里更新更多的读书笔记,当然读书笔记的内容并不完善,如果需要详细内容请购买《React 与 Redux 开发实例精解》


第一篇:纯 React&纯 Redux
https://bubao.github.io/posts/4bb8d0fd.html
作者
一念
发布于
2017年7月1日
许可协议