写在开始
上篇中,完成了 TODO 列表展示, TODO 项状态更改,添加新 TODO。
只是使用的 React Native 方式控制 state
,这里,我们开始使用 Redux 控制 state
,也就是 React Native + Redux 开发。
源码:https://github.com/eylu/web-lib/tree/master/ReactReduxDemo/app_step1
Redux 简介
Redux 三宝: Store 、Action 、Reducer。
Action: 所做操作的描述
其本质上是一个 JavaScript 对象,包括一个必须的字段 type
,以及其它数据项。类似于这样 { type: 'ACTION_NAME', attr1: 'data1', attr2: 'data2' }
。然而,我们会使用函数 ActionCreator(args)
来创建 Action 。
例如:添加 TODO 项,我们会使用下面的函数来创建 Action,带有 TODO 项的名称。并且,我们会把 Action 的 type
字段用常量赋值,不直接使用字符串,以便更好的管理与引用。
const ADD_TODO = 'ADD_TODO';
function addTodo(text){
return {type: ADD_TODO, text}
}
Reducer: 状态 state
更新函数
它是一个纯函数,接收两个参数:state
、action
,返回一个新的 state
(state, action) => state
注意:谨记 reducer 一定要保持纯净。
只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。
举例:
const ADD_TODO = 'ADD_TODO';
function myReducers(state=[], action){
switch(action.type){
case ADD_TODO:
return [
...state,
{
title: action.text,
status: false,
}
]
default:
return state;
}
}
调用:
// 这里是 Action (ActionCreator)
function addTodo(text){
return {type:ADD_TODO, text}
}
// 这里是 State
let state = [{title: '吃早饭',status:true},{title: '打电话',status:false}];
let newState = myReducers(state, addTodo('看电视'));
// newState => [{title: '吃早饭', status: true},{title: '打电话', status: false}, {title: '看电视', status: false} ];
实际应用中,我们并不会这样使用 reducer ,这里只是展示一个返回结果。
实际上,我们会这样使用 reducer
1、创建 store
,将 reducer
作为参数传入
2、使用 store
派发(dispatch
) 操作(action
)
3、store
内部执行 reducer
Store: 状态 state
容器
它不仅是 state
容器,更是将 reducer
与 action
连接到了一起。
Store 有以下职责:
- 维持应用的 state;
- 提供 getState() 方法获取 state;
- 提供 dispatch(action) 方法更新 state;
- 通过 subscribe(listener) 注册监听器;
- 通过 subscribe(listener) 返回的函数注销监听器。
谨记:
Redux 应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合而不是创建多个 store。
创建 Store
store
的创建很简单,使用 redux
提供的 createStore()
,并传入两个参数,reducer 和 state
reducer : 随着应用的壮大,reducer 也变得复杂,我们需要将其拆分成小的 reducer ,并使用 combineReducers()
合并成一个 reducer 。
state : 初始状态,可选。
使用示例:
import { createStore, combineReducers } from 'redux';
function reducers(state, action){ ... }
function actionCreator(args){ ... }
let store = createStore(reducers) // 或者
let store = createStore(reducers, {key1:'data1'})
store.getState()
store.dispatch(actionCreator)
// 使用 combineReducers 创建 Store
function reducer1(state, action){ ... }
function reducer2(state, action){ ... }
let combine = combineReducers({
reducer1,
reducer2
});
let store = createStore(combine)
Redux 使用的三原则
1、Single source of truth
单一数据源。整个应用的state,存储在唯一一个object中,同时也只有一个store用于存储这个object.
2、State is read-only
状态是只读的。唯一能改变state的方法,就是触发action操作。action是用来描述正在发生的事件的一个对象。
3、Changes are made with pure functions
在改变state tree时,用到action,同时也需要编写对应的reducers才能完成state改变操作。
React 与 Redux 配合
其实,Redux 并不是 React 的专属,不一定与 React 配合使用。它可以与 React、Angular、Ember、jQuery 甚至纯 JavaScript 等一起使用。
但是,像 React 这种 state => UI
类型的框架与之配合,使用起来还是很爽的!!
react-redux
我们可以使用 react-redux
这个工具把 React 与 Redux 联系起来。它提供两个功能: Provider
、 Connect
。
Provider : 一个 Component
,用来包裹应用程序的根组件(入口组件),提供 store
属性,以供子组件使用。使用方法类似于这样:
let store = createStore(()=>{});
<Provider store={store}>
<HomeContainer />
</Provider>
Connect : 见名知意,将包装好的组件连接到Redux。尽量只做一个顶层的组件,或者 route 处理。从技术上来说你可以将应用中的任何一个组件 connect() 到 Redux store 中,但尽量避免这么做,因为这个数据流很难追踪。使用方法类似于这样:
export default connect()(HomeContainer) // 它将我们的容器组件 `HomeContainer` 连接到 Redux。
理解数据流
这里是 react 数据流
这里是 react + redux 数据流
不再多说,上代码!!!
React + Redux 开发
新建项目 ReactReduxDemo (从新开始)
react-native init ReactReduxDemo
安装
我们要安装 redux
和 react-redux
,在终端中进入到项目目录,执行安装命令:
cd ReactReduxDemo
npm install redux react-redux --save
我们来看一下项目结构与package,如下所示:
|--ReactReduxDemo
|--__tests__
|--android
|--ios
|--node_modules
|--index.android.js
|--index.ios.js
|--package.json
|--...
package.json文件
{
"name": "ReactReduxDemo",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
"test": "jest"
},
"dependencies": {
"react": "15.4.1",
"react-native": "0.38.0",
"react-redux": "^4.4.6", // react-redux 依赖包
"redux": "^3.6.0", // redux 依赖包
},
"jest": {
"preset": "react-native"
},
"devDependencies": {
"babel-jest": "17.0.2",
"babel-preset-react-native": "1.9.0",
"jest": "17.0.3",
"react-test-renderer": "15.4.1"
}
}
接下来,我们搭建我们的项目结构,并且,前面已经介绍, store 的创建需要 reducer ,状态 state 的更新需要 action。所以,为了方便以后的结构管理,我们先做如下操作:
创建 app 文件夹,
创建 app/index.js 入口文件,
创建 app/components 文件夹,
创建 app/containers 文件夹,
创建 app/reducers 文件夹(存放所有的拆分为小的 reducer 文件),
创建 app/actions 文件夹(存放所有的拆分为小的 action 文件)。
现在,我们的项目结构,如下:
|--ReactReduxDemo
|--__tests__
|--android
|--app
|--actions
|--components
|--containers
|--reducers
|--index.js
|--ios
|--node_modules
|--index.android.js
|--index.ios.js
|--package.json
|--...
项目结构已经搭好,接下来,我们开始
写代码
1、稍微修改 ReactReduxDemo/index.ios.js
文件
import React, {
Component
} from 'react';
import {
AppRegistry,
StyleSheet,
View
} from 'react-native';
import RootWrapper from './app/index'; // 引入入口文件
export default class ReactReduxDemo extends Component {
render() {
return (
<View style={styles.container}>
<RootWrapper />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
}
});
AppRegistry.registerComponent('ReactReduxDemo', () => ReactReduxDemo);
2、入口文件 ReactReduxDemo/app/index.js
,修改如下:
import React, { Component } from 'react';
import {
View,
StyleSheet,
} from 'react-native';
import HomeContainer from './containers/home.container'; // home 容器
export default class RootWrapper extends Component{
render(){
return (
<View style={styles.wrapper}>
<HomeContainer />
</View>
);
}
}
const styles = StyleSheet.create({
wrapper: {
flex: 1,
marginTop: 20,
},
});
容器组件 ReactReduxDemo/app/containers/home.container.js
,如下:
import React, { Component } from 'react';
import {
View,
Text
} from 'react-native';
export default class HomeContainer extends Component{
constructor(props){
super(props);
}
render(){
return (
<View>
<Text>Hello Redux !</Text>
</View>
);
}
}
运行项目,如下所示:
使用 Redux
这里,我们所做任务如下:
- 创建
store
,并带有初始数据 - 使用
Provider
connect()
- 显示初始数据
1、我们先创建一个子组件 TodoListComponent
来展示测试数据
新建文件 ReactReduxDemo/app/components/todo-list.component.js
,如下:
import React, { Component } from 'react';
import {
Text,
View,
StyleSheet,
} from 'react-native';
export default class TodoListComponent extends Component{
constructor(props){
super(props);
}
render(){
return (
<View style={styles.wrapper}>
{this.props.todoList.map((todo, index)=>{
var finishStyle = {textDecorationLine:'line-through', color:'gray'};
return (
<Text style={[styles.todo,todo.status&&finishStyle]}>{todo.title}</Text>
);
})}
</View>
);
}
}
const styles = StyleSheet.create({
wrapper: {
paddingHorizontal: 20,
},
todo: {
paddingVertical: 5,
},
});
这里的子组件 TodoListComponent
只是用 props
接收来自容器组件的数据,并以展示。
将容器组件 HomeContainer
稍作修改:
import React, { Component } from 'react';
import {
View,
Text
} from 'react-native';
import TodoListComponent from '../components/todo-list.component'; // 引入子组件
export default class HomeContainer extends Component{
constructor(props){
super(props);
}
render(){
return (
<View>
<TodoListComponent todoList={[{title:'测试数据'}]} />
</View>
);
}
}
运行项目,如下显示:
2、创建 store
,并带有初始数据
修改入口文件,引入 redux
、react-redux
、reducers
,定义初始数据,创建 store
,使用 Provider
包裹根组件,并带上 store
属性(供被包裹的组件使用)。
ReactReduxDemo/app/index.js
文件,修改如下:
import React, { Component } from 'react';
import {
View,
StyleSheet,
} from 'react-native';
import { createStore } from 'redux'; // 引入 redux 以创建 store
import { Provider } from 'react-redux'; // 引入 react-redux,使用 Provider
import reducers from './reducers/index'; // 引入 reducers
import HomeContainer from './containers/home.container';
// 这是初始数据
const initState = {
todos: [
{title:'吃早饭',status:true},
{title:'打篮球',status:false},
{title:'修电脑',status:false},
],
};
let store = createStore(reducers, initState); // 创建 store
export default class RootWrapper extends Component{
render(){
return (
<Provider store={store}>
<View style={styles.wrapper}>
<HomeContainer />
</View>
</Provider>
);
}
}
const styles = StyleSheet.create({
wrapper: {
flex: 1,
marginTop: 20,
},
});
新建文件 ReactReduxDemo/app/reducers/index.js
(作为所有 reducer
的入口),如下:
import { combineReducers } from 'redux';
// 这是一个空的 reducer , 不做任何处理,返回原始 state
function todoList(state=[], action){
return state;
}
const reducers = combineReducers({
todos: todoList // 这里的 key 要与初始数据的 key 一致
});
export default reducers;
3、使用 react-redux
提供的 connect()
进行连接组件与redux。
容器组件 ReactReduxDemo/app/containers/home.container.js
文件,修改如下:
import React, { Component } from 'react';
import {
View,
Text
} from 'react-native';
import { connect } from 'react-redux'; // 引入 react-redux
import TodoListComponent from '../components/todo-list.component';
class HomeContainer extends Component{ // 这里,HomeContainer不再是默认export
constructor(props){
super(props);
}
render(){
return (
<View>
<TodoListComponent todoList={this.props.todoList} /> // 注意,这里的 todoList 是 mapStateToProps 返回的 key (运行时,注释会报错,请删除注释)
</View>
);
}
}
// 基于全局 state ,哪些 state 是我们想注入的 props
function mapStateToProps(state){
return {
todoList: state.todos, // 将全局的 state 的其中一个 key(即todos) 作为 props 注入
}
}
export default connect(mapStateToProps)(HomeContainer); // 连接组件并export
3、运行项目,如下显示,则说明咱们使用 redux 成功了。
到这里,我们成功的将 React
与 Redux
连接了起来,并显示了初始数据,
下篇中,我们会 dispatch(action) 更新状态操作!!!