react native redux 指南

前言

如果要看理論的童鞋點擊這里 redux中文文檔 或者 redux官方文檔 ,本文不會太刻意去介紹大篇幅的理論欢策,本文不做框架之間的對比捐腿,只給想學redux的童鞋提供實質(zhì)的较店、高效的、易理解的學習參考資源芒珠,分享自己在學習過程中的得到。文章更新完后會比較長搅裙,請耐心閱讀理解皱卓,仔細品味。不熟悉redux也沒關(guān)系部逮,可以跟著文章思路娜汁,將三個demo敲完,相信你一定獲益匪淺兄朋。(文后有彩蛋 )掐禁。

已更新內(nèi)容

  • redux 基本使用 (附demo)
  • redux Middleware使用(附demo)
  • redux 集成 navigation (附demo)

redux 基本使用

模仿官方 Todos demo 的react native版。

redux Middleware使用

模仿官方 async demo 的react native版颅和。

集成react-native-navigation后ios演示圖

集成react-native-navigation后android演示圖

集成react-native-navigation后把前兩個domo綜合傅事。

待更新內(nèi)容

  • 待續(xù)......

為什么我要寫這個demo

有的童鞋可能會有疑問

問:官方不是Todosdemo嗎?為什么還要寫這個demo峡扩?

答:官方的demo都是react的蹭越,而并非react native的。我也找過很多關(guān)于介紹redux的文章教届,但我發(fā)現(xiàn)找到的資料要么太基礎响鹃、要么介紹不全面、提供的demo下載無法使用等等各種問題巍佑,迫使我有了自己動手造輪的沖動茴迁,而且這個demo并非只是介紹關(guān)于redux的基礎的東西,而是通過三套demo實踐連貫的圖文的方式萤衰,讓讀者更好的理解堕义,后面還會陸續(xù)更新在使用redux過程中的得到,希望大家鼓勵支持脆栋。

demo采用的代碼規(guī)范

通常一個大項目到后期是需要很多開發(fā)者參與的倦卖,如果每個開發(fā)者都使用自己的一套代碼規(guī)范做事情,這樣帶來的后果就是:后期的代碼管理工作帶來非常大的麻煩椿争,浪費更多的時間去重構(gòu)怕膛,而且也會讓新人看代碼時理解花更多的時間,還容易把別人帶溝里去秦踪,所以一個大型項目最初構(gòu)建架構(gòu)的時候就必須要遵守一些規(guī)范褐捻。

那么我們怎么能敲出清爽而又優(yōu)雅的代碼呢掸茅?又如何檢查我們代碼質(zhì)量合格呢?
我在這里極力推薦遵守airbnb/javascript的規(guī)范和使用eslint來檢查自己代碼的代碼質(zhì)量(是否遵守了規(guī)范)柠逞,因為它們已經(jīng)得到了很多公司和開發(fā)者的認可昧狮。(這里過多的介紹airbnb eslint,本文只提供思路板壮,想了解更多自行搜索)

在沒有使用代碼規(guī)范前我們可能用各自的風格寫了很多年的代碼了逗鸣,突然要適應這套規(guī)范可能非常不適應,沒關(guān)系绰精,多敲多練習撒璧,時間長了就習慣了,誰還沒有一個過程笨使,過程是痛苦的卿樱,但痛苦過后會給你帶來質(zhì)的升華,自己慢慢領(lǐng)悟體會吧阱表。好的事物東西是會被世界所接受殿如,差的事物最終是要被替代的,所以做為一個合格的程序員(特別是前端程序員)要擁抱變化最爬,因為它會使你變得更加的優(yōu)秀涉馁,得到大眾的認可,除非你不愿意讓自己變得更優(yōu)秀爱致。

redux能幫我們做什么

兩張圖示意:


未使用redux的大型react native項目

使用redux后的大型react native項目

redux特性

  • 單一數(shù)據(jù)源: 整個應用的 state 被儲存在一棵 object tree 中烤送,并且這個 object tree 只存在于唯一一個 store 中。

  • State 是只讀的:唯一改變 state 的方法就是觸發(fā) action糠悯,action 是一個用于描述已發(fā)生事件的普通對象帮坚。

  • 使用純函數(shù)來執(zhí)行修改:為了描述 action 如何改變 state tree ,你需要編寫 reducers互艾。

  • 預見性:所有的用戶的行為都是你提前定義好的试和。

  • 統(tǒng)一管理state:所有的狀態(tài)都在一個store中分配管理。

哪些開發(fā)者和項目適合用redux

這里只針對react native開發(fā)而言:

  • 初級:剛接觸react native我非常不建議去使用纫普,因為你還不知道怎么用它阅悍,建議先達到中級。
  • 中級:使用react native做出一個以上已經(jīng)上架的不復雜的應用 redux昨稼,也可以不使用节视,因為使用它并不能讓你在前期快速的迭代開發(fā),在這樣的項目下使用redux就好比大炮打蚊子假栓,副作用很大寻行。但是可以先了解起來,并發(fā)現(xiàn)它的優(yōu)點匾荆。這類相對簡單的應用:當用戶觸發(fā)一個動作(程序需要setState({xxx:xxx}))的時候應用程序狀態(tài)流程是這樣的:
    簡單的狀態(tài)流程
  • 高級:使用react native做出一個以上已經(jīng)上架的復雜的應用(涉及到即時通訊拌蜘、界面布局比較復雜杆烁,組件嵌套太多層次等),而這類復雜應用:當用戶觸發(fā)一個動作(程序需要setState({xxx:xxx}))的時候應用程序狀態(tài)流程是這樣的:
    復雜的狀態(tài)流程

這種狀態(tài)帶來的后果拦坠,兩方面分析:

  • 性能:祖父子組件之間多余的狀態(tài)傳遞连躏,導致寶貴的內(nèi)存資源浪費,同時界面渲染的速度也會變慢贞滨,自然用戶體驗就變差了。
  • 狀態(tài)管理:當程序不斷的迭代拍棕,界面布局越來越復雜晓铆,必然就會產(chǎn)生許多的state狀態(tài),那你是如何有效的管理這些狀態(tài)绰播?是什么原因?qū)е耈I多次渲染骄噪?是哪一步操作導致的UI組件的變化?在沒有使用redux前你可能已經(jīng)發(fā)現(xiàn)可以使用生命周期函數(shù)中的shouldComponentUpdate來減少子組件中沒必要的渲染蠢箩,但終究解決不了狀態(tài)管理復雜的難題链蕊。
    當你使用redux后,復雜的應用程序狀態(tài)流程是這樣的:
    使用redux后

    看完上面圖文后谬泌,是否很直觀的理解了怎樣的項目才適合用redux呢滔韵,這要感謝@justjavac文章提供的動圖支持。

redux for react native 工作邏輯圖

感謝@黑森林工作室作者提供的清晰的邏輯圖

清晰邏輯圖

redux工程結(jié)構(gòu)分析

我對官方的demo小部分位置做了些改造具體看代碼分析:


image.png

分工明細

  • js/actions
    此文件夾下放內(nèi)容做的事情是:定義用戶行為掌实。
  • js/reducers
    此文件夾下放內(nèi)容做的事情是:響應用戶行為陪蜻,返回改變后的狀態(tài),并發(fā)送到 store贱鼻。
  • js/components
    此文件夾下放內(nèi)容做的事情是:自定義的組件宴卖。
  • js/containers
    此文件夾下放內(nèi)容做的事情是:把components文件夾中涉及到狀態(tài)變化的組件進行第二次封裝。
  • App.js
    入口文件(store在這里)邻悬,為什么我要把store定義在這里症昏? 因為它是唯一的,而且必須使用react-redux提供的Provider組件包裹入口的其他組件才能使redux中的store生效父丰。
  • global.js
    存放全局定義的變量肝谭、常量、方法等础米。

需要注意的事

  • 一個工程中 reduxstore 是唯一的分苇,不能在多個 store
  • 保持 reducer 純凈非常重要屁桑。永遠不要在 reducer 里做這些操作:
  • 修改傳入?yún)?shù)医寿;
  • 執(zhí)行有副作用的操作,如 API 請求和路由跳轉(zhuǎn)蘑斧;
  • 調(diào)用非純函數(shù)靖秩,如 Date.now()Math.random();
  • 使用對象展開運算符...代替Object.assign()才是最好的解決方案须眷。
  • 組件名首字母要大寫,也就是說componentscontainers文件夾下的文件首字母都要大寫沟突。
  • 應該盡量減少傳遞到action 中的數(shù)據(jù)(能傳單個數(shù)據(jù)就不傳對象花颗,能傳對象就不傳數(shù)組)
//good
function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    default:
      return state
  }
}
//best
function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return { ...state, visibilityFilter: action.filter }
    default:
      return state
  }
}

代碼詳解

js/actions/types.js

//添加列表數(shù)據(jù)
export const ADD_TODO = 'ADD_TODO';
//篩選
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';
//文字添加/取消中劃線
export const TOGGLE_TODO = 'TOGGLE_TODO';

釋:

action定義

為什么我要把用戶的action(行為)定義單獨抽出來寫一個type.js

  • 方便狀態(tài)管理惠拭。
  • 復用性扩劝。

js/actions/index.js

import  {
    ADD_TODO,
    SET_VISIBILITY_FILTER,
    TOGGLE_TODO,
} from './types'
let nextTodoId = 0;

export const addTodo = text => ({
    type: ADD_TODO,
    id: nextTodoId++,
    text
});

export const setVisibilityFilter = (filter) => ({
    type: SET_VISIBILITY_FILTER,
    filter
});

export const toggleTodo = id => ({
    type: TOGGLE_TODO,
    id
});

釋:

Action 創(chuàng)建函數(shù)

Action 創(chuàng)建函數(shù) 就是生成 action 的方法≈案ǎ“action” 和 “action 創(chuàng)建函數(shù)” 這兩個概念很容易混在一起棒呛,使用時最好注意區(qū)分。

Redux 中的 action 創(chuàng)建函數(shù)只是簡單的返回一個 action:


js/reducers/todos.js

import  {
    ADD_TODO,
    TOGGLE_TODO,
} from '../actions/types'

const todos = (state = [], action) => {
    let {id, text, type} = action;
    switch (type) {
        case ADD_TODO:
            return [
                ...state,
                {
                    id: id,
                    text: text,
                    completed: false
                }
            ];
        case TOGGLE_TODO:
            return state.map(todo => (todo.id === id) ? {...todo, completed: !todo.completed} : todo);
        default:
            return state;
    }
};

export  default  todos;

js/reducers/visibilityFilter.js

import { SET_VISIBILITY_FILTER } from '../actions/types'
import { visibilityFilters } from '../global'

const { SHOW_ALL } = visibilityFilters;
const visibilityFilter = (state = SHOW_ALL, action) => {
    let {type, filter} = action;
    switch (type){
        case SET_VISIBILITY_FILTER:
            return filter;
        default:
            return state
    }
};

export default  visibilityFilter;

釋:

reducer 就是一個純函數(shù)域携,接收舊的 stateaction簇秒,返回新的 state(上面兩個文件可以看著兩個reducer)。

注意:

  • Redux 首次執(zhí)行時秀鞭,stateundefined趋观,此時需要設置返回應用的初始 state
  • 每個 reducer 只負責管理全局 state 中它負責的一部分锋边。每個 reducerstate 參數(shù)都不同皱坛,分別對應它管理的那部分 state 數(shù)據(jù)。

js/reducers/index.js

import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'

export default combineReducers({
    todos,
    visibilityFilter
})

釋:

combineReducers()所做的只是生成一個函數(shù)宠默,這個函數(shù)來調(diào)用你的一系列 reducer麸恍,每個 reducer 根據(jù)它們的 key 來篩選出 state 中的一部分數(shù)據(jù)并處理,然后這個生成的函數(shù)再將所有 reducer 的結(jié)果合并成一個大的對象搀矫。

表面上看上去combineReducers()的作用就是把多個reducer合成一個的reducer抹沪。


js/components/Todo.js

import React, { Component } from 'react'
import {
    Text,
    TouchableOpacity
} from 'react-native'
import PropTypes from 'prop-types'

export default class Todo extends Component {
    static propTypes = {
        onClick: PropTypes.func.isRequired,
        completed: PropTypes.bool.isRequired,
        text: PropTypes.string.isRequired
    };

    render(){
        let { onClick, completed, text } = this.props;
        return (
            <TouchableOpacity
                style={{
                    flexDirection: 'row',
                    flex: 1,
                    height: 50,
                    alignItems: 'center',
                    justifyContent: 'center',
                    backgroundColor: '#cccccc',
                    marginTop: 10
                }}
                onPress={onClick}>
                <Text style={{ textDecorationLine: completed ? 'line-through' : 'none'}}>{text}</Text>
            </TouchableOpacity>
        );
    }
}

js/components/TodoList.js

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {
    FlatList
} from 'react-native'
import Todo from './Todo'

export default class TodoList extends Component {
    static propTypes = {
        todos: PropTypes.arrayOf(
            PropTypes.shape({
                id: PropTypes.number.isRequired,
                completed: PropTypes.bool.isRequired,
                text: PropTypes.string.isRequired
            }).isRequired
        ).isRequired,
        toggleTodo: PropTypes.func.isRequired
    };

    _renderItem = (data) => {
       let dataItem = data.item;
       let { id } = dataItem;
       let { toggleTodo } = this.props;
        return (
            <Todo
                {...dataItem}
                onClick={() => toggleTodo(id)}
            />
        )
    };

    render() {
        let { todos } = this.props;
        return (
            <FlatList
                data={todos}
                keyExtractor={(item)=>item.id.toString()}
                renderItem={this._renderItem}
            />
        )
    }
}

js/components/Link.js.js

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {
    TouchableOpacity,
    Text
} from 'react-native'

export default class Link extends Component {
    static propTypes = {
        active: PropTypes.bool.isRequired,
        filter: PropTypes.string.isRequired,
        onClick: PropTypes.func.isRequired
    };

    render() {
        let { active,  filter, onClick } = this.props;
        return (
           <TouchableOpacity
               style={{
                   marginLeft: 4,
                   height: 40,
                   flex:1,
                   borderWidth: 1,
                   borderColor: '#ccc',
                   alignItems: 'center',
                   justifyContent:'center'
               }}
               onPress={onClick}
           >
               <Text style={{fontSize: 10, color: active ? 'black' : '#cccccc'}}>{filter}</Text>
           </TouchableOpacity>
        );
    }
}

js/components/Filters.js

import React, { Component } from 'react'
import {
    View,
} from 'react-native'
import FilterLink from '../containers/FilterLink'
import { visibilityFilters } from '../global'

const { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE  } = visibilityFilters;

export default  class Filters extends Component {

    render(){
        return(
            <View style={{ flexDirection: 'row', marginTop: 20}}>
                <FilterLink filter={SHOW_ALL} />
                <FilterLink filter={SHOW_COMPLETED} />
                <FilterLink filter={SHOW_ACTIVE} />
            </View>
        )
    }
}

image.png

釋:

以上四個文件是自定義的四個UI展示組件,這些組件只定義外觀并不關(guān)心數(shù)據(jù)來源和如何改變瓤球。傳入什么就渲染什么融欧。如果你把代碼從 Redux 遷移到別的架構(gòu),這些組件可以不做任何改動直接使用卦羡。它們并不依賴于 Redux噪馏。


js/containers/AddTodo.js

import React, { Component } from 'react'
import {
    View,
    TextInput,
    Button,
} from 'react-native'
import { connect } from 'react-redux'
import { addTodo } from '../actions'

class AddTodo extends Component {
    constructor(props){
        super(props);
        this.inputValue = '';
    }

    render(){
        let { dispatch } = this.props;
        return (
            <View style={{flexDirection: 'row'}}>
                <TextInput
                    style={{flex:1, borderWidth: 1, borderColor: '#cccccc', textAlign: 'center'}}
                    onChangeText={text => this.inputValue = text}
                />
                <Button title="Add Todo" onPress={() => dispatch(addTodo(this.inputValue))}/>
            </View>
        )
    }
}

export default connect()(AddTodo)

js/containers/FilterLink.js

import { connect } from 'react-redux'
import { setVisibilityFilter } from '../actions'
import Link from '../components/Link'

const mapStateToProps = (state, ownProps) => ({
    active: ownProps.filter === state.visibilityFilter,
    filterText: ownProps.filter
});

const mapDispatchToProps = (dispatch, ownProps) => ({
    onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
});

export default connect(
    mapStateToProps,
    mapDispatchToProps,
)(Link)

js/containers/VisibleTodoList.js

import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
import { visibilityFilters } from '../global'

const { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } = visibilityFilters;

const getVisibleTodos = (todos, filter) => {
    switch (filter) {
        case SHOW_COMPLETED:
            return todos.filter(t => t.completed);
        case SHOW_ACTIVE:
            return todos.filter(t => !t.completed);
        case SHOW_ALL:
            return todos;
        default:
            throw new Error('Unknown filter: ' + filter)
    }
};

const mapStateToProps = state => ({
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
});

const mapDispatchToProps = dispatch => ({
    toggleTodo: id => dispatch(toggleTodo(id))
});

export default connect(
    mapStateToProps,
    mapDispatchToProps,
)(TodoList)

釋:

以上三個是容器組件,作用是把展示組件連接到 Redux绿饵。
總之:只要記住一句話就可以了:UI展示組件負責 UI 的呈現(xiàn)欠肾,容器組件負責管理數(shù)據(jù)和邏輯。

有時很難分清到底該使用容器組件還是展示組件拟赊。如這個小的組件:

  • AddTodo.js 含有“Add”按鈕 和 輸入框

技術(shù)上講可以把它分成兩個組件刺桃,但一開始就這么做有點早。在一些非常小的組件里混用容器和展示是可以的吸祟。當業(yè)務變復雜后瑟慈,如何拆分就很明顯了桃移。所以現(xiàn)在就使用混合型的吧。

上面出現(xiàn)了使用react-reduxconnect()方法來把展示組件和容器組件關(guān)聯(lián)在一起葛碧,這個方法做了性能優(yōu)化來避免很多不必要的重復渲染借杰。(這樣你就不必為了性能而手動實現(xiàn) React 性能優(yōu)化建議 中的 shouldComponentUpdate 方法。)

使用 connect() 前进泼,需要先定義 mapStateToProps 這個函數(shù)來指定如何把當前 Redux store state 映射到展示組件的 props 中蔗衡。例如,VisibleTodoList 需要計算傳到 TodoList 中的 todos缘琅,所以定義了根據(jù) state.visibilityFilter 來過濾 state.todos 的方法粘都,并在 mapStateToProps 中使用。

除了讀取 state刷袍,容器組件還能分發(fā) action。類似的方式樊展,可以定義 mapDispatchToProps() 方法接收 dispatch() 方法并返回期望注入到展示組件的 props 中的回調(diào)方法呻纹。例如,我們希望 VisibleTodoListTodoList 組件中注入一個叫 onTodoClick 的 props 专缠,還希望 onTodoClick能分發(fā) TOGGLE_TODO 這個 action雷酪。
最后,使用 connect() 創(chuàng)建 VisibleTodoList涝婉,并傳入這兩個函數(shù)哥力。


js/components/Group.js

import React, { Component } from 'react'
import {
    View
} from 'react-native'
import AddTodo from '../containers/AddTodo'
import Filters from '../components/Filters'
import VisibleTodoList from '../containers/VisibleTodoList'

export default class Group extends Component {
    render() {
        return (
            <View style={{paddingHorizontal: 20, paddingVertical: 44}}>
                <AddTodo/>
                <Filters/>
                <VisibleTodoList/>
            </View>
        );
    }
}

釋:

Group.js 是把所有的關(guān)聯(lián)后的組件串起來,形成一個完整的界面墩弯。


App.js

import React, { Component } from 'react'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import Group from './js/components/Group'
import rootReducer from './js/reducers'

export default class App extends Component {
    render() {
        const store = createStore(rootReducer);
        return (
            <Provider store={store}>
                <Group />
            </Provider>
        );
    }
}

釋:

入口文件傳入 Store

  • 創(chuàng)建store傳入reducers吩跋。
  • 使用Provider組件包裹 Group組件, store作為屬性傳入Provider

進行到這一步渔工,代碼分析完畢锌钮。本次寫作到此結(jié)束。我相信大家如果仔細看完的話引矩,多多少少會有些收獲吧梁丘,如果demo看不太懂,那就跟著代碼分析的思路多敲幾遍代碼旺韭,也就理解了氛谜,有空我會繼續(xù)更新未完成的內(nèi)容。

???????????????? 華麗的分割線 ????????????????

離上次更新已經(jīng)有好幾天了区端,今天抽空更新的內(nèi)容是Middleware(中間件)值漫。

Middleware(中間件)的作用

Middleware是在ActionsDispatcher之間嵌入的為了解決某些問題、提高我們開發(fā)效率而存在的工具珊燎。
下面介紹三種常用的中間件:

  • redux-thunk 中間件:項目中的異步操作需要用到(例如:請求服務器數(shù)據(jù)惭嚣、本地存儲等)遵湖。
  • redux-actions 中間件:幫助處理和創(chuàng)建操作actions(本文不做介紹,后續(xù)項目復雜后可以使用它來創(chuàng)建)晚吞。
  • redux-logger 中間件:用來打印 action 日志延旧。
    開啟react native遠程調(diào)試模式,操作demo就能在控制臺看到打印的狀態(tài)前后變化槽地。
    狀態(tài)日志

加入中間件后的示意圖如下:


加入中間件后的示意圖

核心代碼詳解

本次demo代碼講解為了減少文章篇幅迁沫,只會講解涉及到Middleware的部分,也就是說 demo中在reducers 捌蚊、components 集畅、containers文件加下新增的文件不會做過多的解釋,如果不理解缅糟,可以返回去把第一次更新的內(nèi)容再解析一遍挺智。

actions/types.js新增如下代碼

//請求帖子列表
export const REQUEST_POSTS = 'REQUEST_POSTS';
//帖子返回數(shù)據(jù)
export const RECEIVE_POSTS = 'RECEIVE_POSTS';
//切換數(shù)據(jù)源
export const SELECT_SUBREDDIT = 'SELECT_SUBREDDIT';
//使緩存過期失效
export const INVALIDATE_SUBREDDIT = 'INVALIDATE_SUBREDDIT';

actions/index.js新增如下代碼

export const selectSubreddit = subreddit => ({
    type: SELECT_SUBREDDIT,
    subreddit
});

export const invalidateSubreddit = subreddit => ({
    type: INVALIDATE_SUBREDDIT,
    subreddit
});

export const requestPosts = subreddit => ({
    type: REQUEST_POSTS,
    subreddit
});

export const receivePosts = (subreddit, json) => ({
    type: RECEIVE_POSTS,
    subreddit,
    posts: json.data,
    receivedAt: Date.now()
});

const fetchPosts = subreddit => dispatch => {
    // API 發(fā)起請求
    dispatch(requestPosts(subreddit));
    return fetch(`http://localhost:8081/data/${subreddit}.json`)
        .then(response => response.json())
        .then(json => {
            setTimeout(()=>{
                //使用 API 請求結(jié)果來更新應用的 state
                dispatch(receivePosts(subreddit, json))
            },2000);

        })
};

const shouldFetchPosts = (state, subreddit) => {
    const posts = state.postsBySubreddit[subreddit];
    if (!posts) {
        return true
    }
    if (posts.isFetching) {
        return false
    }
    return posts.didInvalidate
};

export const fetchPostsIfNeeded = subreddit => (dispatch, getState) => {
    if (shouldFetchPosts(getState(), subreddit)) {
        return dispatch(fetchPosts(subreddit))
    }
};

以上主要需要注意的是

  • fetchPosts返回了一個函數(shù),而普通的 Action 創(chuàng)建函數(shù) 默認返回一個對象窗宦。
  • 返回的函數(shù)的參數(shù)是dispatchgetState這兩個 Redux 方法赦颇,普通的 Action 創(chuàng)建函數(shù) 的參數(shù)是 Action 的內(nèi)容。
  • 在返回的函數(shù)之中赴涵,先發(fā)出一個 Action: dispatch(requestPosts(subreddit))媒怯,表示操作開始。
  • 異步操作結(jié)束之后髓窜,再發(fā)出一個 Action: receivePosts(subreddit, json)扇苞,表示操作結(jié)束。

demo中數(shù)據(jù)源解釋:

本來打算用官方的 reddit demo API寄纵,最終發(fā)現(xiàn)官方給出的demo請求數(shù)據(jù)會報錯鳖敷,所以使用了本地的json數(shù)據(jù),延遲兩秒模擬網(wǎng)絡API加載數(shù)據(jù)的過程擂啥。

官方提供的redditAPI無法使用

App.js

import React, { Component } from 'react'
import { applyMiddleware, createStore } from 'redux'
import { createLogger } from 'redux-logger'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'
import LoadPosts from './js/containers/LoadPosts'
import rootReducer from './js/reducers'

export default class App extends Component {
    render() {
        const logger = createLogger();
        const store = createStore(
            rootReducer,
            applyMiddleware(thunk, logger)
        );

        return (
            <Provider store={store}>
                <LoadPosts/>
            </Provider>
        );
    }
}

相比前一個demoApp.js哄陶,在createStore的時候參數(shù)有變化,多了一個applyMiddleware(thunk, logger)中間件的參數(shù)哺壶。
理解了第一次更新內(nèi)容的童鞋不難看出屋吨,Action 是由store.dispatch方法發(fā)送的。而store.dispatch方法正常情況下山宾,參數(shù)只能是對象至扰,不能是函數(shù)。
為了解決這個問題资锰,就要使用到中間件redux-thunk改造store.dispatch敢课,使store.dispatch可以接受函數(shù)作為參數(shù)。

注意

有的中間件有次序要求,使用前要查一下文檔直秆。比如濒募,logger就一定要放在最后,否則輸出結(jié)果會不正確圾结。


到此本次寫作到此結(jié)束瑰剃。

???????????????? 華麗的分割線 ????????????????

接著更新關(guān)于集成navigation的集成,如果使用過比較老版本的react native都知道在react-navigation沒有興起之前筝野,大多數(shù)開發(fā)者都使用的官方提供的 Navigator晌姚,直到 react native v0.44.3 發(fā)布時宣布已經(jīng)遺棄Navigator

image.png

因為我之前項目中只用過 Navigator或者 react-navigation歇竟,所以我并不知道市場上還有多少類似的導航解決方案挥唠,這次通過項目空檔期,又深入了解了一下焕议,目前市場上比較流行的三款導航器:

這是官方推薦的宝磨,在 github 上已有 1.35W+ 的 ??,由React Native社區(qū)維護盅安,目前懊烤,它是最受歡迎的React Native導航庫。它完全用JavaScript編寫宽堆,而不是使用本機API,它重新創(chuàng)建了一些子集茸习。這個選擇允許用戶定制導航體驗的任何部分畜隶,而無需學習iOSAndroid導航邏輯。因為React Navigation的大部分邏輯都是在JavaScript中而不是在本機中運行号胚,所以任何阻止JavaScript線程的情況都會造成卡頓顯現(xiàn)籽慢。另外說明一下 react navigation的v1版本跟v2版本差別挺大的,如果想了解的童鞋可以看我前面寫的這篇文章 react native 強大的navigation V2.0+
猫胁。

目前官方文檔中已經(jīng)明確提出:

Warning: in the next major version of React Navigation, to be released in Fall 2018, we will no longer provide any information about how to integrate with Redux and it may cease to work. Issues related to Redux that are posted on the React Navigation issue tracker will be immediately closed. Redux integration may continue to work but it will not be tested against or considered when making any design decisions for the library.

Some folks like to have their navigation state stored in the same place as the rest of their application state. Think twice before you consider doing this, there is an incredibly good chance that you do not need to do this!. Storing your React Navigation state in your own Redux store is likely to give you a very difficult time if you don't know what you're doing.

If your only reason for doing this is that you want to be able to perform navigation actions from outside of your components (eg: from a Redux middleware), you can learn more about this in navigating without the navigation prop.

翻譯:

警告: 在下一個大版本的 React Navigation 中, 將在2018年秋季發(fā)布, 我們將不再提供有關(guān)如何集成 Redux 的任何信息, 并且它可能會停止使用箱亿。 發(fā)布在 React Navigation issue tracker 中有關(guān) Redux 的 issue,也將立即關(guān)閉弃秆。 Redux 集成可能會繼續(xù)工作届惋,但不會在為 library 作出任何設計決策時進行測試或考慮。

有些人喜歡將他們的 navigation state 存儲在與其他的應用程序的 state 相同的位置菠赚。 在你考慮這樣做之前請三思, 但是有一個非常好的機會, 你可以不需要這樣做!脑豹。 如果你不知道自己要做什么,將 React Navigation state 存儲在你自己的 Redux store 中可能會會很困難衡查。

如果你這樣做的唯一原因是希望能夠從組件外部執(zhí)行導航操作 (例如: 從 Redux 中間件), 你可以了解更多關(guān)于 不使用 navigation prop 進行導航 的信息瘩欺。

翻譯成通俗易懂的話就是:React Navigation在下個版本中將不會再特意考慮去兼容 Redux,用是可以用,但是出了問題需要自行解決俱饿。

哎歌粥,不理解官方為什么要這么做,可能是減少維護成本吧拍埠,但是這樣做無疑是一個不明智但選擇失驶,也說不定會有驚喜,暫時期待一下吧械拍。如果項目中集成了 redux 我個人不太推薦使用React Navigation突勇。

它是基于 React Navigation,但提供了與其交互的不同API坷虑。在 github 上已有 7600+ 的 ??甲馋,它允許您在一個中心位置定義場景轉(zhuǎn)換,而無需傳遞導航器對象迄损,并且可以在代碼中的任何位置輕松訪問定躏。

最新的beta版本 - 4,除了其他更改之外芹敌,還介紹了抽屜支持和Mob-X驅(qū)動的導航狀態(tài)機痊远,它將導航邏輯與表示層分開。

另一個流行的導航庫是由 Wix 開源團隊開發(fā)的React Native Navigation氏捞,在 github 上已經(jīng)接近9000+ 的 ??碧聪,它的最大優(yōu)勢是跨平臺界面覆蓋的100%本機平臺導航,具有開箱即用的Redux支持液茎。

您需要為iOSAndroid單獨配置此軟件包逞姿,其中包括鏈接iOS庫,更新iOS標頭搜索路徑捆等,在Android MainActivity中擴展SplashActivity而不是ReactActivity以及文檔中詳細描述的其他幾個步驟滞造。完成后,您只需要注冊所有應用程序的屏幕并啟動應用程序栋烤。

目前官方文檔中也提出:

Note: example redux is deprecated. Since we did not have enough time and resources to maintain both example projects, we decided to stop maintaining the redux example. This does not mean redux can't be used with react-native-navigation (In fact, we use redux in the Wix app). For a working example project which uses redux with RNN you can refer to JuneDomingo/movieapp.

翻譯:

注意:不推薦使用示例redux谒养。由于我們沒有足夠的時間和資源來維護這兩個示例項目,因此我們決定停止維護redux示例明郭。這并不意味著redux不能與react-native-navigation一起使用(事實上买窟,我們在Wix應用程序中使用redux)第股。對于使用帶RNN的redux的工作示例項目瘪吏,您可以參考JuneDomingo / movieapp。

綜上所訴:就個人而言忌傻,從react navigationreact-native-navigation 官方對 Redux的態(tài)度完全是不一樣的沉唠,至少Wix內(nèi)部在使用Redux疆虚。 如果項目中需要使用Redux,我的第一選擇會是React Native Navigation,因為它是純原生體驗径簿,而且對Redux支持很好 罢屈。如果在不使用Redux 的項目中,可以嘗試前兩種導航篇亭,這兩種導航體驗也不錯的缠捌,非常接近原生體驗了。

結(jié)構(gòu)分析

本文導航選擇使用 react-native-navigation译蒂,關(guān)于react-native-navigation的集成和API使用請參考官方文檔曼月,如果想了解在 React Navigation 中使用 redux 點這里 或者 這里,以下是這次更新改變和新增的文件代碼
index.js

//discard (廢棄)
import { AppRegistry } from 'react-native';
AppRegistry.registerComponent('ReduxForReactNativeDemo', () => App);
//new
import App from './App';
new App();

App.js

import React, { Component } from 'react'
import { applyMiddleware, createStore } from 'redux'
import { createLogger } from 'redux-logger'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'
import rootReducer from './js/reducers'
import { Navigation } from 'react-native-navigation'
import { registerScreens } from './js/components/screens'

const logger = createLogger();
const store = createStore(
    rootReducer,
    applyMiddleware(thunk, logger)
);

registerScreens(store, Provider);

export default class App extends Component {

    constructor(props){
       super(props);
       this._startApp();
    }

    _startApp = () => {
        Navigation.startTabBasedApp({
            tabs: [
                {
                    label: 'Home',
                    screen: 'ReduxForReactNativeDemo.HomeScreen',
                    icon: require('./res/img/ic_home.png'),
                    // selectedIcon: require('./img/checkmark.png'),
                    title: 'Home',
                    overrideBackPress: false,
                    navigatorStyle: {}
                },
                {
                    label: 'Posts',
                    screen: 'ReduxForReactNativeDemo.PostsScreen',
                    icon: require('./res/img/ic_news.png'),
                    // selectedIcon: require('./img/checkmark.png'),
                    title: 'Posts',
                    navigatorStyle: {}

                }
            ]
        });
    }

}

比起上個版本的demo柔昼,整個App.js文件代碼基本都改了


其他改變

components目錄下新增screens目錄哑芹,該文件夾下放一個一個的界面文件,每個界面里面又由多個組件組成捕透。

  • Group.js改名為HomeScreen.js聪姿。
  • 新增PostsDetail.jsPostsScreen.js乙嘀、index.js末购,index.js文件作用是注冊所有界面文件。
  • Posts.js新增item點擊事件虎谢,點擊后進入列表詳細界面盟榴。
  • LoadPosts.js 68 行新增 {...this.props},為了在 Posts.js里面可以通過 this.props獲取到navigator婴噩。
  • 根目錄下新增res資源文件夾曹货。

總結(jié):

本次結(jié)構(gòu)分析就到這里了,說下三個demo版本連貫做下來的感受吧讳推。講真這次對我本人來說學到很多東西,實踐過程中也遇到各種問題玩般,查閱海量資源银觅,有很多疑問,最終一一攻破坏为,答案慢慢浮出水面究驴。看過很多demo千奇百怪的寫法都有匀伏,很少見到標準的項目工程結(jié)構(gòu)洒忧,大多都是為了實現(xiàn)效果為目的,而不能在實際項目中去使用這種項目結(jié)構(gòu)够颠,我文章開始階段我就介紹我了為什么要花這些時間和精力來寫這篇技術(shù)文章熙侍。我會把這種工程結(jié)構(gòu)運用到以后集成了redux 的項目中。找到一份好的學習資料真的很不容易,如果你也覺得不錯的話蛉抓,不妨把 ?? 點亮庆尘,讓更多人發(fā)現(xiàn)它。


彩蛋

附上 demo 巷送,歡迎 ?????? 指出錯誤或者發(fā)布自己的見解探討驶忌,共勉。??

注意

直接 clone 下來運行的話笑跛,默認看到是最后一次(v3)更新的內(nèi)容 demo付魔,
執(zhí)行git tag可以看到的demo有三個 tag,如果切換到前兩次更新的 demo內(nèi)容:根目錄下執(zhí)行:

切換到v0.1

git checkout -b dev v0.1

切換到v0.2

git checkout -b dev v0.2

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末飞蹂,一起剝皮案震驚了整個濱河市几苍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌晤柄,老刑警劉巖擦剑,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異芥颈,居然都是意外死亡惠勒,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門爬坑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纠屋,“玉大人,你說我怎么就攤上這事盾计∈鄣#” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵署辉,是天一觀的道長族铆。 經(jīng)常有香客問我,道長哭尝,這世上最難降的妖魔是什么哥攘? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮材鹦,結(jié)果婚禮上逝淹,老公的妹妹穿的比我還像新娘。我一直安慰自己桶唐,他們只是感情好栅葡,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著尤泽,像睡著了一般欣簇。 火紅的嫁衣襯著肌膚如雪规脸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天醉蚁,我揣著相機與錄音燃辖,去河邊找鬼。 笑死网棍,一個胖子當著我的面吹牛黔龟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播滥玷,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼氏身,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了惑畴?” 一聲冷哼從身側(cè)響起蛋欣,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎如贷,沒想到半個月后陷虎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡杠袱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年尚猿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片楣富。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡凿掂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出纹蝴,到底是詐尸還是另有隱情庄萎,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布塘安,位于F島的核電站糠涛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏兼犯。R本人自食惡果不足惜脱羡,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望免都。 院中可真熱鬧,春花似錦帆竹、人聲如沸绕娘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽险领。三九已至侨舆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間绢陌,已是汗流浹背挨下。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留脐湾,地道東北人臭笆。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像秤掌,于是被迫代替她去往敵國和親愁铺。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容