前言
如果要看理論的童鞋點(diǎn)擊這里 redux中文文檔 或者 redux官方文檔 ,本文不會太刻意去介紹大篇幅的理論,本文不做框架之間的對比,只給想學(xué)redux的童鞋提供實(shí)質(zhì)的、高效的、易理解的學(xué)習(xí)參考資源,分享自己在學(xué)習(xí)過程中的得到。文章更新完后會比較長,請耐心閱讀理解,仔細(xì)品味。不熟悉redux也沒關(guān)系,可以跟著文章思路,將三個(gè)demo敲完,相信你一定獲益匪淺。(文后有彩蛋 )。
已更新內(nèi)容
-
redux基本使用 (附demo) -
reduxMiddleware使用(附demo) -
redux集成navigation(附demo)
模仿官方 Todos demo 的
react native版。
模仿官方 async demo 的
react native版。


集成react-native-navigation后把前兩個(gè)domo綜合。
待更新內(nèi)容
- 待續(xù)......
為什么我要寫這個(gè)demo
有的童鞋可能會有疑問
問:官方不是Todosdemo嗎?為什么還要寫這個(gè)demo?
答:官方的demo都是react的,而并非react native的。我也找過很多關(guān)于介紹redux的文章,但我發(fā)現(xiàn)找到的資料要么太基礎(chǔ)、要么介紹不全面、提供的demo下載無法使用等等各種問題,迫使我有了自己動手造輪的沖動,而且這個(gè)demo并非只是介紹關(guān)于redux的基礎(chǔ)的東西,而是通過三套demo實(shí)踐連貫的圖文的方式,讓讀者更好的理解,后面還會陸續(xù)更新在使用redux過程中的得到,希望大家鼓勵(lì)支持。
demo采用的代碼規(guī)范
通常一個(gè)大項(xiàng)目到后期是需要很多開發(fā)者參與的,如果每個(gè)開發(fā)者都使用自己的一套代碼規(guī)范做事情,這樣帶來的后果就是:后期的代碼管理工作帶來非常大的麻煩,浪費(fèi)更多的時(shí)間去重構(gòu),而且也會讓新人看代碼時(shí)理解花更多的時(shí)間,還容易把別人帶溝里去,所以一個(gè)大型項(xiàng)目最初構(gòu)建架構(gòu)的時(shí)候就必須要遵守一些規(guī)范。
那么我們怎么能敲出清爽而又優(yōu)雅的代碼呢?又如何檢查我們代碼質(zhì)量合格呢?
我在這里極力推薦遵守airbnb/javascript的規(guī)范和使用eslint來檢查自己代碼的代碼質(zhì)量(是否遵守了規(guī)范),因?yàn)樗鼈円呀?jīng)得到了很多公司和開發(fā)者的認(rèn)可。(這里過多的介紹airbnb eslint,本文只提供思路,想了解更多自行搜索)
在沒有使用代碼規(guī)范前我們可能用各自的風(fēng)格寫了很多年的代碼了,突然要適應(yīng)這套規(guī)范可能非常不適應(yīng),沒關(guān)系,多敲多練習(xí),時(shí)間長了就習(xí)慣了,誰還沒有一個(gè)過程,過程是痛苦的,但痛苦過后會給你帶來質(zhì)的升華,自己慢慢領(lǐng)悟體會吧。好的事物東西是會被世界所接受,差的事物最終是要被替代的,所以做為一個(gè)合格的程序員(特別是前端程序員)要擁抱變化,因?yàn)樗鼤鼓阕兊酶拥膬?yōu)秀,得到大眾的認(rèn)可,除非你不愿意讓自己變得更優(yōu)秀。
redux能幫我們做什么
兩張圖示意:
redux特性
單一數(shù)據(jù)源: 整個(gè)應(yīng)用的 state 被儲存在一棵 object tree 中,并且這個(gè) object tree 只存在于唯一一個(gè) store 中。
State 是只讀的:唯一改變 state 的方法就是觸發(fā) action,action 是一個(gè)用于描述已發(fā)生事件的普通對象。
使用純函數(shù)來執(zhí)行修改:為了描述 action 如何改變 state tree ,你需要編寫 reducers。
預(yù)見性:所有的用戶的行為都是你提前定義好的。
統(tǒng)一管理state:所有的狀態(tài)都在一個(gè)store中分配管理。
哪些開發(fā)者和項(xiàng)目適合用redux
這里只針對react native開發(fā)而言:
- 初級:剛接觸
react native我非常不建議去使用,因?yàn)槟氵€不知道怎么用它,建議先達(dá)到中級。 - 中級:使用
react native做出一個(gè)以上已經(jīng)上架的不復(fù)雜的應(yīng)用redux,也可以不使用,因?yàn)槭褂盟⒉荒茏屇阍谇捌诳焖俚牡_發(fā),在這樣的項(xiàng)目下使用redux就好比大炮打蚊子,副作用很大。但是可以先了解起來,并發(fā)現(xiàn)它的優(yōu)點(diǎn)。這類相對簡單的應(yīng)用:當(dāng)用戶觸發(fā)一個(gè)動作(程序需要setState({xxx:xxx}))的時(shí)候應(yīng)用程序狀態(tài)流程是這樣的:
簡單的狀態(tài)流程
- 高級:使用
react native做出一個(gè)以上已經(jīng)上架的復(fù)雜的應(yīng)用(涉及到即時(shí)通訊、界面布局比較復(fù)雜,組件嵌套太多層次等),而這類復(fù)雜應(yīng)用:當(dāng)用戶觸發(fā)一個(gè)動作(程序需要setState({xxx:xxx}))的時(shí)候應(yīng)用程序狀態(tài)流程是這樣的:
復(fù)雜的狀態(tài)流程
這種狀態(tài)帶來的后果,兩方面分析:
- 性能:祖父子組件之間多余的狀態(tài)傳遞,導(dǎo)致寶貴的內(nèi)存資源浪費(fèi),同時(shí)界面渲染的速度也會變慢,自然用戶體驗(yàn)就變差了。
- 狀態(tài)管理:當(dāng)程序不斷的迭代,界面布局越來越復(fù)雜,必然就會產(chǎn)生許多的
state狀態(tài),那你是如何有效的管理這些狀態(tài)?是什么原因?qū)е耈I多次渲染?是哪一步操作導(dǎo)致的UI組件的變化?在沒有使用redux前你可能已經(jīng)發(fā)現(xiàn)可以使用生命周期函數(shù)中的shouldComponentUpdate來減少子組件中沒必要的渲染,但終究解決不了狀態(tài)管理復(fù)雜的難題。
當(dāng)你使用redux后,復(fù)雜的應(yīng)用程序狀態(tài)流程是這樣的:
使用redux后
看完上面圖文后,是否很直觀的理解了怎樣的項(xiàng)目才適合用redux呢,這要感謝@justjavac文章提供的動圖支持。
redux for react native 工作邏輯圖
感謝@黑森林工作室作者提供的清晰的邏輯圖
redux工程結(jié)構(gòu)分析
我對官方的demo小部分位置做了些改造具體看代碼分析:
分工明細(xì)
-
js/actions
此文件夾下放內(nèi)容做的事情是:定義用戶行為。 -
js/reducers
此文件夾下放內(nèi)容做的事情是:響應(yīng)用戶行為,返回改變后的狀態(tài),并發(fā)送到store。 -
js/components
此文件夾下放內(nèi)容做的事情是:自定義的組件。 -
js/containers
此文件夾下放內(nèi)容做的事情是:把components文件夾中涉及到狀態(tài)變化的組件進(jìn)行第二次封裝。 -
App.js
入口文件(store在這里),為什么我要把store定義在這里? 因?yàn)樗俏ㄒ坏?,而且必須使?code>react-redux提供的Provider組件包裹入口的其他組件才能使redux中的store生效。 -
global.js
存放全局定義的變量、常量、方法等。
需要注意的事
- 一個(gè)工程中
redux的store是唯一的,不能在多個(gè)store。 - 保持
reducer純凈非常重要。永遠(yuǎn)不要在reducer里做這些操作:
- 修改傳入?yún)?shù);
- 執(zhí)行有副作用的操作,如
API請求和路由跳轉(zhuǎn);- 調(diào)用非純函數(shù),如
Date.now()或Math.random();
- 使用對象展開運(yùn)算符
...代替Object.assign()才是最好的解決方案。 - 組件名首字母要大寫,也就是說
components和containers文件夾下的文件首字母都要大寫。 - 應(yīng)該盡量減少傳遞到
action中的數(shù)據(jù)(能傳單個(gè)數(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(行為)定義單獨(dú)抽出來寫一個(gè)type.js?
- 方便狀態(tài)管理。
- 復(fù)用性。
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 的方法?!?code>action” 和 “action 創(chuàng)建函數(shù)” 這兩個(gè)概念很容易混在一起,使用時(shí)最好注意區(qū)分。
在 Redux 中的 action 創(chuàng)建函數(shù)只是簡單的返回一個(gè) 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 就是一個(gè)純函數(shù),接收舊的 state 和 action,返回新的 state(上面兩個(gè)文件可以看著兩個(gè)reducer)。
注意:
Redux首次執(zhí)行時(shí),state為undefined,此時(shí)需要設(shè)置返回應(yīng)用的初始state。- 每個(gè)
reducer只負(fù)責(zé)管理全局state中它負(fù)責(zé)的一部分。每個(gè)reducer的state參數(shù)都不同,分別對應(yīng)它管理的那部分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()所做的只是生成一個(gè)函數(shù),這個(gè)函數(shù)來調(diào)用你的一系列 reducer,每個(gè) reducer 根據(jù)它們的 key 來篩選出 state 中的一部分?jǐn)?shù)據(jù)并處理,然后這個(gè)生成的函數(shù)再將所有 reducer 的結(jié)果合并成一個(gè)大的對象。
表面上看上去combineReducers()的作用就是把多個(gè)reducer合成一個(gè)的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>
)
}
}
釋:
以上四個(gè)文件是自定義的四個(gè)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)
釋:
以上三個(gè)是容器組件,作用是把展示組件連接到 Redux。
總之:只要記住一句話就可以了:UI展示組件負(fù)責(zé) UI 的呈現(xiàn),容器組件負(fù)責(zé)管理數(shù)據(jù)和邏輯。
有時(shí)很難分清到底該使用容器組件還是展示組件。如這個(gè)小的組件:
AddTodo.js含有“Add”按鈕 和 輸入框
技術(shù)上講可以把它分成兩個(gè)組件,但一開始就這么做有點(diǎn)早。在一些非常小的組件里混用容器和展示是可以的。當(dāng)業(yè)務(wù)變復(fù)雜后,如何拆分就很明顯了。所以現(xiàn)在就使用混合型的吧。
上面出現(xiàn)了使用react-redux的connect()方法來把展示組件和容器組件關(guān)聯(lián)在一起,這個(gè)方法做了性能優(yōu)化來避免很多不必要的重復(fù)渲染。(這樣你就不必為了性能而手動實(shí)現(xiàn) React 性能優(yōu)化建議 中的 shouldComponentUpdate 方法。)
使用 connect() 前,需要先定義 mapStateToProps 這個(gè)函數(shù)來指定如何把當(dāng)前 Redux store state 映射到展示組件的 props 中。例如,VisibleTodoList 需要計(jì)算傳到 TodoList 中的 todos,所以定義了根據(jù) state.visibilityFilter 來過濾 state.todos 的方法,并在 mapStateToProps 中使用。
除了讀取 state,容器組件還能分發(fā) action。類似的方式,可以定義 mapDispatchToProps() 方法接收 dispatch() 方法并返回期望注入到展示組件的 props 中的回調(diào)方法。例如,我們希望 VisibleTodoList 向 TodoList 組件中注入一個(gè)叫 onTodoClick 的 props ,還希望 onTodoClick能分發(fā) TOGGLE_TODO 這個(gè) action。
最后,使用 connect() 創(chuàng)建 VisibleTodoList,并傳入這兩個(gè)函數(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)后的組件串起來,形成一個(gè)完整的界面。
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。
進(jìn)行到這一步,代碼分析完畢。本次寫作到此結(jié)束。我相信大家如果仔細(xì)看完的話,多多少少會有些收獲吧,如果demo看不太懂,那就跟著代碼分析的思路多敲幾遍代碼,也就理解了,有空我會繼續(xù)更新未完成的內(nèi)容。
???????????????? 華麗的分割線 ????????????????
離上次更新已經(jīng)有好幾天了,今天抽空更新的內(nèi)容是Middleware(中間件)。
Middleware(中間件)的作用
Middleware是在Actions和Dispatcher之間嵌入的為了解決某些問題、提高我們開發(fā)效率而存在的工具。
下面介紹三種常用的中間件:
- redux-thunk 中間件:項(xiàng)目中的異步操作需要用到(例如:請求服務(wù)器數(shù)據(jù)、本地存儲等)。
-
redux-actions 中間件:幫助處理和創(chuàng)建操作
actions(本文不做介紹,后續(xù)項(xiàng)目復(fù)雜后可以使用它來創(chuàng)建)。 -
redux-logger 中間件:用來打印
action日志。
開啟react native遠(yuǎn)程調(diào)試模式,操作demo就能在控制臺看到打印的狀態(tài)前后變化。
狀態(tài)日志
加入中間件后的示意圖如下:
核心代碼詳解
本次demo代碼講解為了減少文章篇幅,只會講解涉及到Middleware的部分,也就是說 demo中在reducers 、components 、containers文件加下新增的文件不會做過多的解釋,如果不理解,可以返回去把第一次更新的內(nèi)容再解析一遍。
actions/types.js新增如下代碼
//請求帖子列表
export const REQUEST_POSTS = 'REQUEST_POSTS';
//帖子返回?cái)?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é)果來更新應(yīng)用的 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返回了一個(gè)函數(shù),而普通的Action 創(chuàng)建函數(shù)默認(rèn)返回一個(gè)對象。- 返回的函數(shù)的參數(shù)是
dispatch和getState這兩個(gè)Redux方法,普通的Action 創(chuàng)建函數(shù)的參數(shù)是Action的內(nèi)容。- 在返回的函數(shù)之中,先發(fā)出一個(gè)
Action: dispatch(requestPosts(subreddit)),表示操作開始。- 異步操作結(jié)束之后,再發(fā)出一個(gè)
Action: receivePosts(subreddit, json),表示操作結(jié)束。
demo中數(shù)據(jù)源解釋:
本來打算用官方的 reddit demo API,最終發(fā)現(xiàn)官方給出的
demo請求數(shù)據(jù)會報(bào)錯(cuò),所以使用了本地的json數(shù)據(jù),延遲兩秒模擬網(wǎng)絡(luò)API加載數(shù)據(jù)的過程。
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>
);
}
}
釋
相比前一個(gè)demo的App.js,在createStore的時(shí)候參數(shù)有變化,多了一個(gè)applyMiddleware(thunk, logger)中間件的參數(shù)。
理解了第一次更新內(nèi)容的童鞋不難看出,Action 是由store.dispatch方法發(fā)送的。而store.dispatch方法正常情況下,參數(shù)只能是對象,不能是函數(shù)。
為了解決這個(gè)問題,就要使用到中間件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ā)布時(shí)宣布已經(jīng)遺棄Navigator。

因?yàn)槲抑绊?xiàng)目中只用過
Navigator或者 react-navigation,所以我并不知道市場上還有多少類似的導(dǎo)航解決方案,這次通過項(xiàng)目空檔期,又深入了解了一下,目前市場上比較流行的三款導(dǎo)航器:
這是官方推薦的,在
github上已有1.35W+的 ??,由React Native社區(qū)維護(hù),目前,它是最受歡迎的React Native導(dǎo)航庫。它完全用JavaScript編寫,而不是使用本機(jī)API,它重新創(chuàng)建了一些子集。這個(gè)選擇允許用戶定制導(dǎo)航體驗(yàn)的任何部分,而無需學(xué)習(xí)iOS或Android導(dǎo)航邏輯。因?yàn)?code>React Navigation的大部分邏輯都是在JavaScript中而不是在本機(jī)中運(yùn)行,所以任何阻止JavaScript線程的情況都會造成卡頓顯現(xiàn)。另外說明一下react navigation的v1版本跟v2版本差別挺大的,如果想了解的童鞋可以看我前面寫的這篇文章 react native 強(qiáng)大的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.
翻譯:
警告: 在下一個(gè)大版本的 React Navigation 中, 將在2018年秋季發(fā)布, 我們將不再提供有關(guān)如何集成 Redux 的任何信息, 并且它可能會停止使用。 發(fā)布在 React Navigation issue tracker 中有關(guān) Redux 的 issue,也將立即關(guān)閉。 Redux 集成可能會繼續(xù)工作,但不會在為 library 作出任何設(shè)計(jì)決策時(shí)進(jìn)行測試或考慮。
有些人喜歡將他們的 navigation state 存儲在與其他的應(yīng)用程序的 state 相同的位置。 在你考慮這樣做之前請三思, 但是有一個(gè)非常好的機(jī)會, 你可以不需要這樣做!。 如果你不知道自己要做什么,將 React Navigation state 存儲在你自己的 Redux store 中可能會會很困難。
如果你這樣做的唯一原因是希望能夠從組件外部執(zhí)行導(dǎo)航操作 (例如: 從 Redux 中間件), 你可以了解更多關(guān)于 不使用 navigation prop 進(jìn)行導(dǎo)航 的信息。
翻譯成通俗易懂的話就是:React Navigation在下個(gè)版本中將不會再特意考慮去兼容 Redux,用是可以用,但是出了問題需要自行解決。
哎,不理解官方為什么要這么做,可能是減少維護(hù)成本吧,但是這樣做無疑是一個(gè)不明智但選擇,也說不定會有驚喜,暫時(shí)期待一下吧。如果項(xiàng)目中集成了 redux 我個(gè)人不太推薦使用React Navigation。
它是基于
React Navigation,但提供了與其交互的不同API。在github上已有7600+的 ??,它允許您在一個(gè)中心位置定義場景轉(zhuǎn)換,而無需傳遞導(dǎo)航器對象,并且可以在代碼中的任何位置輕松訪問。最新的beta版本 - 4,除了其他更改之外,還介紹了抽屜支持和Mob-X驅(qū)動的導(dǎo)航狀態(tài)機(jī),它將導(dǎo)航邏輯與表示層分開。
另一個(gè)流行的導(dǎo)航庫是由 Wix 開源團(tuán)隊(duì)開發(fā)的
React Native Navigation,在github上已經(jīng)接近9000+的 ??,它的最大優(yōu)勢是跨平臺界面覆蓋的100%本機(jī)平臺導(dǎo)航,具有開箱即用的Redux支持。您需要為
iOS和Android單獨(dú)配置此軟件包,其中包括鏈接iOS庫,更新iOS標(biāo)頭搜索路徑,在AndroidMainActivity中擴(kuò)展SplashActivity而不是ReactActivity以及文檔中詳細(xì)描述的其他幾個(gè)步驟。完成后,您只需要注冊所有應(yīng)用程序的屏幕并啟動應(yīng)用程序。
目前官方文檔中也提出:
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。由于我們沒有足夠的時(shí)間和資源來維護(hù)這兩個(gè)示例項(xiàng)目,因此我們決定停止維護(hù)redux示例。這并不意味著redux不能與react-native-navigation一起使用(事實(shí)上,我們在Wix應(yīng)用程序中使用redux)。對于使用帶RNN的redux的工作示例項(xiàng)目,您可以參考JuneDomingo / movieapp。
綜上所訴:就個(gè)人而言,從react navigation 和 react-native-navigation 官方對 Redux的態(tài)度完全是不一樣的,至少Wix內(nèi)部在使用Redux。 如果項(xiàng)目中需要使用Redux,我的第一選擇會是React Native Navigation,因?yàn)樗羌冊w驗(yàn),而且對Redux支持很好 。如果在不使用Redux 的項(xiàng)目中,可以嘗試前兩種導(dǎo)航,這兩種導(dǎo)航體驗(yàn)也不錯(cuò)的,非常接近原生體驗(yàn)了。
結(jié)構(gòu)分析
本文導(dǎo)航選擇使用 react-native-navigation,關(guān)于react-native-navigation的集成和API使用請參考官方文檔,如果想了解在 React Navigation 中使用 redux 點(diǎn)這里 或者 這里,以下是這次更新改變和新增的文件代碼
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: {}
}
]
});
}
}
比起上個(gè)版本的demo,整個(gè)App.js文件代碼基本都改了
其他改變
在components目錄下新增screens目錄,該文件夾下放一個(gè)一個(gè)的界面文件,每個(gè)界面里面又由多個(gè)組件組成。
-
Group.js改名為HomeScreen.js。 - 新增
PostsDetail.js、PostsScreen.js、index.js,index.js文件作用是注冊所有界面文件。 -
Posts.js新增item點(diǎn)擊事件,點(diǎn)擊后進(jìn)入列表詳細(xì)界面。 -
LoadPosts.js68行新增{...this.props},為了在Posts.js里面可以通過this.props獲取到navigator。 - 根目錄下新增
res資源文件夾。
總結(jié):
本次結(jié)構(gòu)分析就到這里了,說下三個(gè)demo版本連貫做下來的感受吧。講真這次對我本人來說學(xué)到很多東西,實(shí)踐過程中也遇到各種問題,查閱海量資源,有很多疑問,最終一一攻破,答案慢慢浮出水面。看過很多demo千奇百怪的寫法都有,很少見到標(biāo)準(zhǔn)的項(xiàng)目工程結(jié)構(gòu),大多都是為了實(shí)現(xiàn)效果為目的,而不能在實(shí)際項(xiàng)目中去使用這種項(xiàng)目結(jié)構(gòu),我文章開始階段我就介紹我了為什么要花這些時(shí)間和精力來寫這篇技術(shù)文章。我會把這種工程結(jié)構(gòu)運(yùn)用到以后集成了redux 的項(xiàng)目中。找到一份好的學(xué)習(xí)資料真的很不容易,如果你也覺得不錯(cuò)的話,不妨把 ?? 點(diǎn)亮,讓更多人發(fā)現(xiàn)它。
彩蛋
附上 demo ,歡迎 ?????? 指出錯(cuò)誤或者發(fā)布自己的見解探討,共勉。??
注意
直接 clone 下來運(yùn)行的話,默認(rèn)看到是最后一次(v3)更新的內(nèi)容 demo,
執(zhí)行git tag可以看到的demo有三個(gè) tag,如果切換到前兩次更新的 demo內(nèi)容:根目錄下執(zhí)行:
切換到v0.1
git checkout -b dev v0.1
切換到v0.2
git checkout -b dev v0.2