看了我这篇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阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容