一步一步學習 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) 更新狀態操作!!!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容