看了我這篇RN你就入門了

前言

React認為每個組件都是一個有限狀態機,狀態與UI是一一對應的。我們只需管理好APP的state就能控制UI的顯示,我們可以在每個component類中來通過this.statethis.setState來管理組件的state,但是如果APP交互比較多比較復雜,或者說該組件的某一狀態需要和其他組件共享的話,這種方式就有點復雜了。
有沒有一種能統一管理APP狀態的框架呢,這時候Redux就應用而生了,它是一個用于統一管理APP 所有的state的一個的js框架,它不建議我們在component中直接操作state,而是交給redux的store中進行處理。而react-redux又是redux的作者為react專門做的一個封裝,適用于react的狀態管理容器。

那么我們的項目到底需不需要redux呢,從組件角度看,如果你的應用有以下場景,可以考慮使用 Redux,否則無腦強行使用反而麻煩。

  • 某個組件的狀態,需要共享
  • 某個狀態需要在任何地方都可以拿到
  • 一個組件需要改變全局狀態
  • 一個組件需要改變另一個組件的狀態

redux的設計思想

  • Web 應用是一個狀態機,視圖與狀態是一一對應的。
  • 所有的狀態,保存在store對象里面,由其統一管理。
  • 狀態在組件中是‘只讀’的,要交給redux處理

redux數據流程圖

有圖有真相,先來一張redux數據流圖,讓你有一個整體的把握


redux flow

redux的相關概念

Action

Action 是把數據從應用(這里之所以不叫 view 是因為這些數據有可能是服務器響應,用戶輸入或其它非 view 的數據 )傳到 store 的有效載荷。它是 store 數據的唯一來源。一般來說你會通過 store.dispatch() 將 action 傳到 store。

按redux設計理念是不允許用戶直接操作組件的state,而是通過觸發動作(action)來更新state,用戶或后臺服務器可以通過store.dispatch(action)來向store觸發一個消息(消息至少一個標識該消息的字段type,還可以添加其他字段用于數據傳送),store會在內部根據消息的類型type去reducer中執行相應的處理,這個消息我們就叫他為Action,Action本質上是一個JavaScript對象。

實際編碼中一般會把整個應用的消息類型type統一放在一個文件ActionTypes.js中

export const ADD_TODO = 'ADD_TODO'

Action的結構如下,各個字段的key的名字可以隨意命名,但是類型的key一般都是type,數據類型最好為字符串

{
  type: ADD_TODO,
  text: 'Build my first Redux app'
}

隨著程序越來越大,你會發現一個組件中的action太多太亂了,所以我們也會把action按業務分類放在各個指定的文件中,但是又有一個問題,若果每個action的字段都有五六個,我們在如下寫法豈不是太亂了

store.dispatch({
  type: ADD_TODO,
  text: 'Build my first Redux app'
})

于是乎我們就想起來可以將action對象封裝在函數中,這個函數返回一個action對象,這個返回一個action對象的函數我們就稱之為 action creator,如下所示

export let todo = ()=> {
    return {
        type: ADD_TODO,
        text: 'Build my first Redux app'
    }
}

我們直接store.dispatch(todo)就好了,看著是不是整潔多了啊

異步動作(async action)
事實上redux提供的dispatch方法只能接受純粹的action對象(即js中Object類型的對象),如下所示:

store.dispatch({
  type: ADD_TODO,
  text: 'Build my first Redux app'
})

這也是我們最常用的方式,這種方式屬于同步Action,一旦觸發同步動作,redux就能同步的完成state的操作,但是我們在開發中會遇到這么一類acton,比如網絡請求,這是一個異步的過程,此時純粹的action對象已經滿足不了,我們需要采用異步Action,異步Action本質上是一個函數,我們可以在此函數的回調中去調用dispatch來觸發action,而這一次的觸發是同步的,如下所示:

// 來看一下我們寫的第一個 thunk action 創建函數!
// 雖然內部操作不同,你可以像其它 action 創建函數 一樣使用它:
// store.dispatch(fetchPosts('reactjs'))

export function fetchPosts(subreddit) {

  // Thunk middleware 知道如何處理函數。
  // 這里把 dispatch 方法通過參數的形式傳給函數,
  // 以此來讓它自己也能 dispatch action。
  return function (dispatch) {

    // 首次 dispatch:更新應用的 state 來通知
    // API 請求發起了。此時我們可以做一些操作,比如讓網絡指示器顯示出來
    dispatch(requestPosts(subreddit))

    // thunk middleware 調用的函數可以有返回值,
    // 它會被當作 dispatch 方法的返回值傳遞
    return fetch(`http://www.subreddit.com/r/${subreddit}.json`)
      .then(
        response => response.json(),
        // 不要使用 catch,因為會捕獲
        // 在 dispatch 和渲染中出現的任何錯誤,
        // 導致 'Unexpected batch number' 錯誤。
        // https://github.com/facebook/react/issues/6895
         error => console.log('An error occurred.', error)
      )
      .then(json =>
        // 可以多次 dispatch,這里的dispatch是同步action
        // 在這里我們可以把異步操作的結果 同步的觸發 給redux,同時隱藏網絡指示器(即改變網絡指示器的狀態)
        dispatch(receivePosts(subreddit, json))
      )
  }
}

前面不是說dispatch只能接受js對象嗎,現在怎么可以接受一個函數了?其實這正是thunk middleware(中間件)的功能,他把原來的dispatch進行了包裝,進行了一系列的預處理,具體細節可以另行參考其他資源,這里不再詳述了。

reducer

它是一個純函數,要求有相同的輸入(參數)就一定會有相同的輸出,里面不能包含一些不確定的因素,比如new Date(),它會根據當前的state和action來進行邏輯處理返回一個新的state
參數一:當前的state對象
參數二:action對象
返回值:產生一個新的state對象

import { VisibilityFilters } from './actions'
//初始state
const initialState = {
  visibilityFilter: VisibilityFilters.SHOW_ALL,
  todos: []
};

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    default:
      return state
  }
}

注意:reducer函數中一定不要去修改state,而是用Object.assign()函數生成一個新的state對象,如上所示

combineReducers:隨著應用變得復雜,把APP的所有狀態都放在一個reducer中處理會造成reducer函數非常龐大,因此需要對 reducer 函數 進行拆分,拆分后的每一個子reducer獨立負責管理 APP state 的一部分。combineReducers 輔助函數的作用是,把多個不同子reducer 函數合并成一個最終的根reducer ,最后將根 reducer 作為createStore的參數就可以創建store對象了。合并后的 reducer 可以調用各個子 reducer,并把它們的結果合并成一個 state 對象。state 對象的結構由傳入的多個 reducer 的 key 決定。

最終,state 對象的結構會是這樣的:

{
  reducer1: ...
  reducer2: ...
}

使用方法如下所示

import { combineReducers } from 'redux';
import Strolling from './strollingReducer';
import Foods from './foodsReducer';
import FoodsList from './foodsListReducer';
import FoodCompare from './foodCompareReducer';
import FoodInfo from './foodInfoReducer';
import Search from './searchReducer';
import User from './userReducer';

export default rootReducer = combineReducers({
    Strolling,
    Foods,
    FoodsList,
    FoodCompare,
    FoodInfo,
    Search,
    User,
})

// export default rootReducer = combineReducers({
//     Strolling:Strolling,
//     Foods:Foods,  
//     FoodsList:FoodsList,
//     FoodCompare:FoodCompare,
//     FoodInfo:FoodInfo,
//     Search:Search,
//     User:User,
// })
 
// export default function rootReducer(state = {},action){

//     return{
//         Strolling: Strolling(state.Strolling,action),
//         Foods:Foods(state.Foods,action),
//         FoodsList:FoodsList(state.FoodsList,action),
//         FoodCompare:FoodCompare(state.FoodCompare,action),
//         FoodInfo:FoodInfo(state.FoodInfo,action),
//         Search:Search(state.Search,action),
//         User:User(state.User,action)
//     }
// }

//以上三種方式是等價的,key可以設置也可以省略

注意:我們不一定非要用combineReducers來組合子reducer,我們可以自定義類似功能的方法來組合,state的結構完全由我們決定。

store

一個應用只有一個store,store 就是用來維持應用所有的 state 樹 的一個對象。 改變 store 內 state 的惟一途徑是對它 dispatch 一個 action,它有三個函數

  • getState()
    返回應用當前的 state 樹。
  • dispatch(action)
    分發 action。這是觸發 state 變化的惟一途徑。
    會使用當前 getState() 的結果和傳入的 action 以同步方式的調用 store 的 reduce 函數。返回值會被作為下一個 state。從現在開始,這就成為了 getState() 的返回值,同時變化監聽器(change listener)會被觸發。
  • subscribe(listener)
    當state樹發生變化的時候store會調用subscribe函數,我們可以傳一個我們訂制的函數作為參數來進行處理
    參數:一個函數
    返回值:返回一個解綁定函數
    //添加監聽
    let unsubscribe = store.subscribe(handleChange)
    //解除監聽
    unsubscribe()
    
  • replaceReducer(nextReducer)
    替換 store 當前用來計算 state 的 reducer。
    這是一個高級 API。只有在你需要實現代碼分隔,而且需要立即加載一些 reducer 的時候才可能會用到它。在實現 Redux 熱加載機制的時候也可能會用到。

react-redux基礎

前言已經提到過react-redux的由來,這里在啰嗦一下,react-redux是redux作者專門為react訂制的,這樣使用起來更方便,我們只需在我們的組件中通過屬性props獲取dispatch方法,就可以直接向store發送一個action,而不需要再獲取store對象,通過store.dispatch方法發送。

react-redux有兩寶,providerconnect,下面詳細介紹一下。

Provider:

有一個store屬性,我們要將應用的根組件放到Provider標簽中,這樣應用的所有的子組件就可以通過context來獲取store對象了,但是我們一般不會通過此法來獲取store對象,Provider是為了給connect函數使用的,這樣才能通過connect函數的參數獲取到store的state和dispatch了。

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

connect是一個高階函數,connect()本身會返回一個函數變量(假如名字為func),給這個函數變量傳遞一個參數func(MainContainer)會生成一個MainContainer容器組件,形如下面的寫法:

export default connect((state) => {
    const { Main } = state;
    return {
        Main
    }
})(MainContainer);

參數一:[mapStateToProps(state, [ownProps]): stateProps] (Function)

如果定義該參數,組件將會監聽 Redux store 的變化。任何時候,只要 Redux store 發生改變,mapStateToProps 函數就會被調用。該回調函數必須返回一個純對象,這個對象會與組件的 props 合并。如果你省略了這個參數,你的組件將不會監聽 Redux store。如果指定了該回調函數中的第二個參數 ownProps,則該參數的值為傳遞到組件的 props,而且只要組件接收到新的 props,mapStateToProps 也會被調用(例如,當 props 接收到來自父組件一個小小的改動,那么你所使用的 ownProps 參數,mapStateToProps 都會被重新計算)。

參數二:[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function):

如果傳遞的是一個對象,那么每個定義在該對象的函數都將被當作 Redux action creator,而且這個對象會與 Redux store 綁定在一起,其中所定義的方法名將作為屬性名,合并到組件的 props 中。如果傳遞的是一個函數,該函數將接收一個 dispatch 函數,然后由你來決定如何返回一個對象,這個對象通過 dispatch 函數與 action creator 以某種方式綁定在一起(提示:你也許會用到 Redux 的輔助函數 bindActionCreators())。如果你省略這個 mapDispatchToProps 參數,默認情況下,dispatch 會注入到你的組件 props 中。如果指定了該回調函數中第二個參數 ownProps,該參數的值為傳遞到組件的 props,而且只要組件接收到新 props,mapDispatchToProps 也會被調用。

參數三:[mergeProps(stateProps, dispatchProps, ownProps): props] (Function)

如果指定了這個參數,mapStateToProps() 與 mapDispatchToProps() 的執行結果和組件自身的 props 將傳入到這個回調函數中。該回調函數返回的對象將作為 props 傳遞到被包裝的組件中。你也許可以用這個回調函數,根據組件的 props 來篩選部分的 state 數據,或者把 props 中的某個特定變量與 action creator 綁定在一起。如果你省略這個參數,默認情況下返回 Object.assign({}, ownProps, stateProps, dispatchProps) 的結果。
[options] (Object) 如果指定這個參數,可以定制 connector 的行為。

參數四:[options] (Object) 如果指定這個參數,可以定制 connector 的行為。

[pure = true] (Boolean): 如果為 true,connector 將執行 shouldComponentUpdate 并且淺對比 mergeProps 的結果,避免不必要的更新,前提是當前組件是一個“純”組件,它不依賴于任何的輸入或 state 而只依賴于 props 和 Redux store 的 state。默認值為 true。
[withRef = false] (Boolean): 如果為 true,connector 會保存一個對被包裝組件實例的引用,該引用通過 getWrappedInstance() 方法獲得。默認值為 false。

redux-redux使用

上面說了provider和connect方法,下面是實用講解

創建store對象的js文件

下面的代碼里包括應用中間件redux-thunk,和創建store對象兩步,這里有更多關于中間件的詳情

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers/rootRudcer';
//使用thunk中間件
let createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
//創建store對象,一個APP只有一個store對象
let store = createStoreWithMiddleware(rootReducer);
export default store;

程序的入口文件

import React from 'react';
import { Provider } from 'react-redux';
import store from './store/store';

import App from './containers/app';

export default class Root extends React.Component {
    render() {
        return (
            //將APP的根視圖組件包含在provider標簽中
            <Provider store = {store} >
                <App />
            </Provider>
        )
    }
}

在容器組件中,將redux和組件關聯起來生成一個容器組件,這里是redux與組件關聯的地方,大多數童鞋使用redux最迷惑的地方估計就在這一塊了。

import React from 'react';
import {connect} from 'react-redux';
import Brand from '../Components/Brand';

//BrandContainer容器組件
class BrandContainer extends React.Component {
    
    render() {
        return (
            //把容器組件的屬性傳遞給UI組件
            <Brand {...this.props} />
        )
    }
}

export default connect((state) => {
    const { BrandReducer } = state;
    return {
        BrandReducer
    }
})(BrandContainer);

這樣UI組件Brand中就可以通過屬性獲取dispatch方法以及處理后的最新state了

const {dispatch, BrandReducer} = this.props;

下面來解釋一下上面的代碼

將當前的BrandContainer組件關聯起來,上面介紹了store中的state對象的結構會是這樣的:

{ 
  reducer1: ... 
  reducer2: ... 
}

所以可以通過解構的方式,獲取對應模塊的state,如下面的const { BrandReducer } = state;

下面這一塊代碼的作用就是將store中state傳遞給關聯的容器組件中,當store中的state發生變化的時候,connect的第一參數mapStateToProps回調函數就會被調用,并且將該回調函數的返回值映射成其關聯組件的一個屬性,這樣容器組件的屬性就會發生變化,而UI組件又通過{...this.props}將容器組件的屬性傳遞給了UI組件,所以UI組件的屬性也會發生變化,我們知道屬性的變化會導致UI組件重新render。好了,我們就能知道為什么我們在UI組件中dispatch一個action后UI組件能更新了,因為UI組件的屬性發生變化導致RN重繪了UI。

react native 組件的生命周期

弄明白了這個圖我認為你就能基本掌握RN了(圖片來自互聯網,【注意圖中有錯誤】最后end的時候是componentWillUnmount)

react-native生命周期

項目的推薦目錄

這種結構適合業務邏輯不太復雜的中小型項目,其優點是邏輯模塊清晰,缺點是文件目錄跨度較大,對于大型項目建議按項目的功能模塊來劃分。


小型項目的推薦目錄

【小型項目建議結構】
utils -- 定義一些工具類,比如網絡請求、本地緩存類、加解密工具類等
common -------- 定義一些通用樣式,以及通用常量,如actionType等
components ----- 定義一些通用組件
pages ------------- 業務組件
containers ------- 定義redux的容器組件
actions ------------ 定義一系列action creator
reduces ---------- 定義reducer
store --------------- 定義把子reducer綁定成一個rootReducer來創建store
navigator---------- 注冊路由
root.js ------------ 根組件

【復雜項目建議結構】
utils -- 定義一些工具類,比如網絡請求、本地緩存類、加解密工具類等
common -------- 定義一些通用樣式,以及通用常量,如actionType等
components ----- 定義一些通用組件
業務模塊一:
pages ------------- 業務組件
containers ------- 定義redux的容器組件
actions ------------ 定義一系列action creator
reduces ---------- 定義reducer
業務模塊二:
pages ------------- 業務組件
containers ------- 定義redux的容器組件
actions ------------ 定義一系列action creator
reduces ---------- 定義reducer

navigator---------- 注冊路由
store --------------- 定義把子reducer綁定成一個rootReducer來創建store
root.js ------------ 根組件

熱更新

目前市場主流的熱更新庫為code-push和jspatch,但現在發現用了jspatch庫上架AppStore會被拒,所以這里主要用code-push來做講解。

code-push是微軟推出的一個熱更新庫。。。。。。

相關文章

React 實踐心得:react-redux 之 connect 方法詳解
Redux 入門教程(一):基本用法
redux中文文檔
react + redux 完整的項目,同時寫一下個人感悟

注:部分圖片來源于互聯網

原文鏈接,轉載請注明此鏈接

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容