一步一步学习 ReactNative + Redux(1)

写在开始

上篇中,完成了 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 更新函数

它是一个纯函数,接收两个参数:stateaction,返回一个新的 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 容器,更是将 reduceraction 连接到了一起。
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 联系起来。它提供两个功能: ProviderConnect
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 数据流.png

这里是 react + redux 数据流

react + redux 数据流.png

不再多说,上代码!!!

React + Redux 开发

新建项目 ReactReduxDemo (从新开始)

react-native init ReactReduxDemo

安装

我们要安装 reduxreact-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>
        );
    }
}

运行项目,如下所示:

Paste_Image.png

使用 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>
        );
    }
}

运行项目,如下显示:

Paste_Image.png

2、创建 store,并带有初始数据
修改入口文件,引入 reduxreact-reduxreducers ,定义初始数据,创建 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 成功了。

Paste_Image.png

到这里,我们成功的将 ReactRedux 连接了起来,并显示了初始数据,
下篇中,我们会 dispatch(action) 更新状态操作!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容