前言
如果要看理論的童鞋點擊這里 redux中文文檔 或者 redux官方文檔 ,本文不會太刻意去介紹大篇幅的理論欢策,本文不做框架之間的對比捐腿,只給想學redux
的童鞋提供實質(zhì)的较店、高效的、易理解的學習參考資源芒珠,分享自己在學習過程中的得到。文章更新完后會比較長搅裙,請耐心閱讀理解皱卓,仔細品味。不熟悉redux
也沒關(guān)系部逮,可以跟著文章思路娜汁,將三個demo
敲完,相信你一定獲益匪淺兄朋。(文后有彩蛋 )掐禁。
已更新內(nèi)容
-
redux
基本使用 (附demo) -
redux
Middleware
使用(附demo) -
redux
集成navigation
(附demo)
模仿官方 Todos demo 的
react native
版。
模仿官方 async demo 的
react native
版颅和。
集成react-native-navigation
后把前兩個domo
綜合傅事。
待更新內(nèi)容
- 待續(xù)......
為什么我要寫這個demo
有的童鞋可能會有疑問
問:官方不是Todos
demo嗎?為什么還要寫這個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特性
單一數(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小部分位置做了些改造具體看代碼分析:
分工明細
-
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
存放全局定義的變量肝谭、常量、方法等础米。
需要注意的事
- 一個工程中
redux
的store
是唯一的分苇,不能在多個store
。 - 保持
reducer
純凈非常重要屁桑。永遠不要在reducer
里做這些操作:
- 修改傳入?yún)?shù)医寿;
- 執(zhí)行有副作用的操作,如
API
請求和路由跳轉(zhuǎn)蘑斧;- 調(diào)用非純函數(shù)靖秩,如
Date.now()
或Math.random()
;
- 使用對象展開運算符
...
代替Object.assign()
才是最好的解決方案须眷。 - 組件名首字母要大寫,也就是說
components
和containers
文件夾下的文件首字母都要大寫沟突。 - 應該盡量減少傳遞到
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ù)域携,接收舊的 state
和 action
簇秒,返回新的 state
(上面兩個文件可以看著兩個reducer
)。
注意:
Redux
首次執(zhí)行時秀鞭,state
為undefined
趋观,此時需要設置返回應用的初始state
。- 每個
reducer
只負責管理全局state
中它負責的一部分锋边。每個reducer
的state
參數(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>
)
}
}
釋:
以上四個文件是自定義的四個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-redux
的connect()
方法來把展示組件和容器組件關(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)方法呻纹。例如,我們希望 VisibleTodoList
向 TodoList
組件中注入一個叫 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
是在Actions
和Dispatcher
之間嵌入的為了解決某些問題、提高我們開發(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ù)是
dispatch
和getState
這兩個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ù)的過程擂啥。
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>
);
}
}
釋
相比前一個demo
的App.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
。
因為我之前項目中只用過
Navigator
或者 react-navigation
歇竟,所以我并不知道市場上還有多少類似的導航解決方案挥唠,這次通過項目空檔期,又深入了解了一下焕议,目前市場上比較流行的三款導航器:
這是官方推薦的宝磨,在
github
上已有1.35W+
的 ??,由React Native
社區(qū)維護盅安,目前懊烤,它是最受歡迎的React Native
導航庫。它完全用JavaScript
編寫宽堆,而不是使用本機API
,它重新創(chuàng)建了一些子集茸习。這個選擇允許用戶定制導航體驗的任何部分畜隶,而無需學習iOS
或Android
導航邏輯。因為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
支持液茎。您需要為
iOS
和Android
單獨配置此軟件包逞姿,其中包括鏈接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 navigation
和 react-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.js
、PostsScreen.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