看了我這篇RN你就入門了

前言

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

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

  • 某個(gè)組件的狀態(tài),需要共享
  • 某個(gè)狀態(tài)需要在任何地方都可以拿到
  • 一個(gè)組件需要改變?nèi)譅顟B(tài)
  • 一個(gè)組件需要改變另一個(gè)組件的狀態(tài)

redux的設(shè)計(jì)思想

  • Web 應(yīng)用是一個(gè)狀態(tài)機(jī),視圖與狀態(tài)是一一對應(yīng)的。
  • 所有的狀態(tài),保存在store對象里面,由其統(tǒng)一管理。
  • 狀態(tài)在組件中是‘只讀’的,要交給redux處理

redux數(shù)據(jù)流程圖

有圖有真相,先來一張redux數(shù)據(jù)流圖,讓你有一個(gè)整體的把握


redux flow

redux的相關(guān)概念

Action

Action 是把數(shù)據(jù)從應(yīng)用(這里之所以不叫 view 是因?yàn)檫@些數(shù)據(jù)有可能是服務(wù)器響應(yīng),用戶輸入或其它非 view 的數(shù)據(jù) )傳到 store 的有效載荷。它是 store 數(shù)據(jù)的唯一來源。一般來說你會(huì)通過 store.dispatch() 將 action 傳到 store。

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

實(shí)際編碼中一般會(huì)把整個(gè)應(yīng)用的消息類型type統(tǒng)一放在一個(gè)文件ActionTypes.js中

export const ADD_TODO = 'ADD_TODO'

Action的結(jié)構(gòu)如下,各個(gè)字段的key的名字可以隨意命名,但是類型的key一般都是type,數(shù)據(jù)類型最好為字符串

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

隨著程序越來越大,你會(huì)發(fā)現(xiàn)一個(gè)組件中的action太多太亂了,所以我們也會(huì)把a(bǔ)ction按業(yè)務(wù)分類放在各個(gè)指定的文件中,但是又有一個(gè)問題,若果每個(gè)action的字段都有五六個(gè),我們在如下寫法豈不是太亂了

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

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

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

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

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

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

這也是我們最常用的方式,這種方式屬于同步Action,一旦觸發(fā)同步動(dòng)作,redux就能同步的完成state的操作,但是我們在開發(fā)中會(huì)遇到這么一類acton,比如網(wǎng)絡(luò)請求,這是一個(gè)異步的過程,此時(shí)純粹的action對象已經(jīng)滿足不了,我們需要采用異步Action,異步Action本質(zhì)上是一個(gè)函數(shù),我們可以在此函數(shù)的回調(diào)中去調(diào)用dispatch來觸發(fā)action,而這一次的觸發(fā)是同步的,如下所示:

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

export function fetchPosts(subreddit) {

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

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

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

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

reducer

它是一個(gè)純函數(shù),要求有相同的輸入(參數(shù))就一定會(huì)有相同的輸出,里面不能包含一些不確定的因素,比如new Date(),它會(huì)根據(jù)當(dāng)前的state和action來進(jìn)行邏輯處理返回一個(gè)新的state
參數(shù)一:當(dāng)前的state對象
參數(shù)二:action對象
返回值:產(chǎn)生一個(gè)新的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函數(shù)中一定不要去修改state,而是用Object.assign()函數(shù)生成一個(gè)新的state對象,如上所示

combineReducers:隨著應(yīng)用變得復(fù)雜,把APP的所有狀態(tài)都放在一個(gè)reducer中處理會(huì)造成reducer函數(shù)非常龐大,因此需要對 reducer 函數(shù) 進(jìn)行拆分,拆分后的每一個(gè)子reducer獨(dú)立負(fù)責(zé)管理 APP state 的一部分。combineReducers 輔助函數(shù)的作用是,把多個(gè)不同子reducer 函數(shù)合并成一個(gè)最終的根reducer ,最后將根 reducer 作為createStore的參數(shù)就可以創(chuàng)建store對象了。合并后的 reducer 可以調(diào)用各個(gè)子 reducer,并把它們的結(jié)果合并成一個(gè) state 對象。state 對象的結(jié)構(gòu)由傳入的多個(gè) reducer 的 key 決定。

最終,state 對象的結(jié)構(gòu)會(huì)是這樣的:

{
  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)
//     }
// }

//以上三種方式是等價(jià)的,key可以設(shè)置也可以省略

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

store

一個(gè)應(yīng)用只有一個(gè)store,store 就是用來維持應(yīng)用所有的 state 樹 的一個(gè)對象。 改變 store 內(nèi) state 的惟一途徑是對它 dispatch 一個(gè) action,它有三個(gè)函數(shù)

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

react-redux基礎(chǔ)

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

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

Provider:

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

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

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

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

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

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

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

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

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

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

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

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

redux-redux使用

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

創(chuàng)建store對象的js文件

下面的代碼里包括應(yīng)用中間件redux-thunk,和創(chuàng)建store對象兩步,這里有更多關(guān)于中間件的詳情

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers/rootRudcer';
//使用thunk中間件
let createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
//創(chuàng)建store對象,一個(gè)APP只有一個(gè)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標(biāo)簽中
            <Provider store = {store} >
                <App />
            </Provider>
        )
    }
}

在容器組件中,將redux和組件關(guān)聯(lián)起來生成一個(gè)容器組件,這里是redux與組件關(guān)聯(lián)的地方,大多數(shù)童鞋使用redux最迷惑的地方估計(jì)就在這一塊了。

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;

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

將當(dāng)前的BrandContainer組件關(guān)聯(lián)起來,上面介紹了store中的state對象的結(jié)構(gòu)會(huì)是這樣的:

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

所以可以通過解構(gòu)的方式,獲取對應(yīng)模塊的state,如下面的const { BrandReducer } = state;

下面這一塊代碼的作用就是將store中state傳遞給關(guān)聯(lián)的容器組件中,當(dāng)store中的state發(fā)生變化的時(shí)候,connect的第一參數(shù)mapStateToProps回調(diào)函數(shù)就會(huì)被調(diào)用,并且將該回調(diào)函數(shù)的返回值映射成其關(guān)聯(lián)組件的一個(gè)屬性,這樣容器組件的屬性就會(huì)發(fā)生變化,而UI組件又通過{...this.props}將容器組件的屬性傳遞給了UI組件,所以UI組件的屬性也會(huì)發(fā)生變化,我們知道屬性的變化會(huì)導(dǎo)致UI組件重新render。好了,我們就能知道為什么我們在UI組件中dispatch一個(gè)action后UI組件能更新了,因?yàn)閁I組件的屬性發(fā)生變化導(dǎo)致RN重繪了UI。

react native 組件的生命周期

弄明白了這個(gè)圖我認(rèn)為你就能基本掌握RN了(圖片來自互聯(lián)網(wǎng),【注意圖中有錯(cuò)誤】最后end的時(shí)候是componentWillUnmount)

react-native生命周期

項(xiàng)目的推薦目錄

這種結(jié)構(gòu)適合業(yè)務(wù)邏輯不太復(fù)雜的中小型項(xiàng)目,其優(yōu)點(diǎn)是邏輯模塊清晰,缺點(diǎn)是文件目錄跨度較大,對于大型項(xiàng)目建議按項(xiàng)目的功能模塊來劃分。


小型項(xiàng)目的推薦目錄

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

【復(fù)雜項(xiàng)目建議結(jié)構(gòu)】
utils -- 定義一些工具類,比如網(wǎng)絡(luò)請求、本地緩存類、加解密工具類等
common -------- 定義一些通用樣式,以及通用常量,如actionType等
components ----- 定義一些通用組件
業(yè)務(wù)模塊一:
pages ------------- 業(yè)務(wù)組件
containers ------- 定義redux的容器組件
actions ------------ 定義一系列action creator
reduces ---------- 定義reducer
業(yè)務(wù)模塊二:
pages ------------- 業(yè)務(wù)組件
containers ------- 定義redux的容器組件
actions ------------ 定義一系列action creator
reduces ---------- 定義reducer

navigator---------- 注冊路由
store --------------- 定義把子reducer綁定成一個(gè)rootReducer來創(chuàng)建store
root.js ------------ 根組件

熱更新

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

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

相關(guān)文章

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

注:部分圖片來源于互聯(lián)網(wǎng)

原文鏈接,轉(zhuǎn)載請注明此鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容