RN学习笔记2-Redux

一、背景

React或者说ReactNative原生使用state和props管理UI状态,但是俩属性非常琐碎,稍微复杂一点的UI就没法应付,所以Redux应运而生,但是Redux又很乱,我觉得乱主要是以下原因:

  • 需要声明的东西多
  • 需要声明的地方多
  • API方法的命名很莫名其妙

下面就来总结一下使用

二、安装

需要在项目里添加如下依赖(版本无所谓)

    "react-redux": "^5.0.7",
    "redux": "^4.0.0",
    "redux-logger": "^3.0.6"

因为redux其实是可以独立运行的js项目,所以把他使用在react项目中,还需要使用react-redux,
redux-logger是打印redux事件log的中间件,具体内容我们后面会说

三、大牛文章

我主要是看这几个文章入门的
https://segmentfault.com/a/1190000008741380
https://www.cnblogs.com/hhhyaaon/p/5860159.html
https://codesandbox.io/s/6n20nrzlxz c11(讲pure redux)
https://segmentfault.com/a/1190000008322583

四、原理

借鉴于https://segmentfault.com/a/1190000008322583
  • redux是一个存储状态,响应事件动作(action)的地方,所以定义redux实现的叫store
  • store有一个初始状态(default state),还有响应某个动作(action)的处理器(reducer)
  • 然后UI视图将这个store及其状态(state)和方法(action)注册到视图组件的props,这样就可以在组件中取到这些状态和方法了。
  • 当用户点击了某个操作等,会从props中拿到action并调用它,他会向store发送(dispatch)这个action的内容,
  • 如果store中有中间件,会先逐个调用中间件来完成预处理
  • 然后再调用各个reducer,来完成状态的改变。
  • 状态改变以后,因为状态绑定了UI组件的props,所以react会自动刷新UI。

那么我们来仔细说一下reducer和中间件
reducer:名字起源于Array的reduce方法,作者估计向表达的是遍历的意义,但是这个名字实在是诡异,所以我给他起名叫做处理器,或者叫事件触发器,作用就是UI发来action以后,它根据action的类型,对状态进行修改。他是action的消费者,他是个函数(或者专业点叫纯函数),但是他有个缺点,就是需要立即返回,如果是网络请求等异步操作,他就没法胜任了。
中间件:中间件的作用就是完成异步请求,或者完成其他一些需要封装起来的预处理,比如redux-logger,就是把action前后的状态打印出来的中间件,本质也是个函数,但是结构很诡异,诡异程度类似于C语言中的3级指针,这个指针还尼玛是函数指针。不过这种诡异我们不需要操心,只需要填写内容

五、RTFSC

(一)redux创建

仿照大牛们的例子,我们做个通过加减按钮改变数值的功能

  1. 首先我们引入redux模块
import { combineReducers, createStore, applyMiddleware, compose } from 'redux'
  1. 创建action creator
    action其实就是个对象,有一个最基本的key是type,表示action的类型,如果业务需要,还可以增加其他key,反正这个对象怎么用也是你自己的事,根据自己喜好来。
    而所谓action creator就是个创建action对象的函数
// action types
export const INCREASE = 'INCREASE'
export const DECREASE = 'DECREASE'
export const RESET = 'RESET'

// actions
const increase = () => ({ type: INCREASE })
const decrease = () => ({ type: DECREASE })
const reset = (num) => ({ type: RESET, num })//除了type,你还可以加别的内容

  1. 定义初始状态
const defaultState = {
  count: 5
}
  1. 创建reducer
    前面说了,reducer其实就是个函数,接受两个参数,一个是当前状态,一个是发来的action,然后返回一个新的状态
function counter (state = defaultState, action) {//有个默认参数,当第一次调用的时候,使用初始状态
  switch (action.type) {
    case INCREASE:
      return { ...state, count: state.count + 1 }
    case DECREASE:
      return { ...state, count: state.count - 1 }
    case RESET:
      return { ...state, count: action.num }
    default:
      return state
  }
}
  1. 创建store
const reducers = combineReducers({counter})

const configureStore = preloadedState => {
  return createStore(
    reducers,
    preloadedState,
    compose(
      applyMiddleware(createLogger)
    )
  )
}
const store = configureStore()
  • combineReducers的意思是把多个reducer合并成一个,因为一个store可以处理很多类的业务,所以可以封装成多个reducer,当action来的时候,会一一调用,所以保证事件唯一性,还是要靠定义唯一的action type
  • 显然preloadedState我们这里没有用,我们是放在了上面的defaultState里,
  • createStore方法返回一个store实例,调用的时候需要把reducers放进去,如果有中间件,就依次放到applyMiddleware里,applyMiddleware支持多个参数
  1. 最后,我们把外界需要的变量导出
export {store, increase, decrease, reset}

store需要放到Provider组件里,包在我们的页面上,用于往页面的props里注入属性和action方法,后面一个就是action creator了,外界调用这个方法来改变store的状态

(二)redux使用

我看到的demo里,都是使用页面根节点来绑定store,根据我的理解,我觉得每个功能点的逻辑,最好分开,所以把一整个store以及所有state定义在根节点,无论对代码整理还是性能都是不可接受的,所以我google了很久终于在so上找到了解决方案。

  1. 首先定义一个页面叫做ReduxPage:
class ReduxPage extends Component {
  constructor (props) {
    super(props)
    this.state = {}
  }

  render () {
    const {increase, decrease, reset} = this.props
    return (
      <View style={styles.container}>
        <Text style={styles.counter}>{this.props.counter.count}</Text>
        <SubText {...this.props} />
        <TouchableOpacity style={styles.btn} onPress={() => { reset(0) }}>
          <Text>归零</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.btn} onPress={increase}>
          <Text>加1</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.btn} onPress={decrease}>
          <Text>减1</Text>
        </TouchableOpacity>
      </View>

    )
  }
}

一个Text显示计数器数字,一个SubText展示子组件显示计数器数字
三个按钮展示动作action

  1. 然后绑定store的state和action方法到这个页面
const mapStateToProps = state => ({
  counter: state.counter
})
const mapDispatchToProps = dispatch => (bindActionCreators({increase, decrease, reset}, dispatch))
//或者const mapDispatchToProps = {increase, decrease, reset}
let Container = connect(mapStateToProps, mapDispatchToProps)(ReduxPage)
  • mapStateToProps是把store中的状态映射给这个页面的props,这个例子是把reducer counter的状态(存储在state.counter中)映射到this.props.counter下
  • mapDispatchToProps及其后面的调用是把action creator绑定上dispatch,注入到this.props下,
    什么意思呢,比如increase函数,他本身是返回一个对象,怎么才能发送给store呢?需要调用this.props.dispatch(increase());dispatch方法是redux放到props里的,用来发送action。bindActionCreators作用是把increase函数替换成dispatch(increase()),名字还叫increase,这样我们调用this.props.increase就可以直接给store发送action了
    这需要注意,其实我们不需要bindActionCreators也可以,因为:传入一个object,其中这个object所对应的value必须是actionCreator,这样redux里面会自动帮我们调用bindActionCreator,所以mapDispatchToProps里传一个object,里面是所有需要的action creator就行
  • connect函数接受两个参数,然后返回一个函数,这个返回函数的参数是UI组件,最终返回一个高阶组件,我们命名为Container
  • 之所以这么啰嗦,其实就是为了套用es6和react的语法,这些都是语法糖
  1. 导出页面组件

我们不能直接导出刚刚创建的ReduxPage,因为现在还没有人绑定store呢,使用下面的代码来绑定store并导出组件

export default class extends Component {
  render () {
    return (<Provider store={store}>
      <Container />
    </Provider>)
  }
};

在其他地方,比如父组件,想怎么用就怎么用就可以了。

(三)redux难题

  1. 自定义子组件继承父组件的redux props
    我们在redux connect的那个组件上,因为有mapStateToProps和mapDispatchToProp函数,可以在其this.props里获取到映射的状态和action,但是如果在这个组件上再嵌套一个自定义的子组件(例如我定义的SubText),在子组件里就获取不到状态和action了,但是我看教程里明明说的是可以自动往下层传的,所以我这里使用了这样的语法,也是google的
 <SubText {...this.props} />

我觉得这只能算是个workaround,正规的写法是啥还没搜出来

  1. 映射方法的时候,定义一个key
    如果我们不想污染this.props,像state一样可以通过this.props.<key>.xxx来获取方法,可以这样写
const mapDispatchToProps = dispatch => ({businessDispatch: bindActionCreators({sortChange, filterChange, fetchData}, dispatch)})
  1. React Navigation

redux和redux navigation合起来使用会比较麻烦,官方有教程
但是因为我的redux只是我当前一个页面的业务redux,没必要和redux navigation混合起来,所以我的解决方案:最外层是react navigation,内层是redux

export default class extends Component {
  static navigationOptions = ({ navigation }) => {
    const params = navigation.state.params || {}

    return {
      title: '客户列表',
      headerRight: (//导航栏按钮
        <View style={{flexDirection: 'row'}}>
          <TouchableOpacity
            style={{width: 40, height: 40, alignItems: 'center', justifyContent: 'center'
            }}
            onPress={params.search}
          >
            <Image source={require('../resource/search.png')} style={{width: 20, height: 20}} />
          </TouchableOpacity>
          <TouchableOpacity
            style={{width: 40, height: 40, alignItems: 'center', justifyContent: 'center'
            }}
            onPress={params.showMore}
          >
            <Image source={require('../resource/more.png')} style={{width: 20, height: 20}} />
          </TouchableOpacity>
        </View>

      )
    }
  };
  render () {
    return (<Provider store={store}>
      <Container navigation={this.props.navigation} />
    </Provider>)
  }
};

  1. 自己创建中间件
    初次看到中间件的时候感觉好高深,好难懂,好晦涩,其实根本就不是,js让我这个objcer懵逼的地方就在于乱七八糟的风格,这个本质就是个函数,其余都可以忽略
    我现在就遇到一个情况,需要下拉刷新请求数据,这是个异步操作,reducer没法处理,所以用中间件。
    这个方法拦截Action.FETCH_DATA(其实就是个提前定义好的字符串,表示具体action type) action, 然后延时刷新数据,刷新数据前后向store发送处理数据中的状态,所谓的刷新数据就是个延时。
function createFetchCustom ({ dispatch, getState }) {
  return (next) =>
    (action) => {
      const prevState = getState()
      const returnValue = next(action)
      const nextState = getState()
      const actionType = String(action.type)
      const customState = nextState.customerListBusiness
      if (actionType === Action.FETCH_DATA) {
        console.log('middle ware fetch data')
        dispatch(Action.processingdata(true))
        const customs = [
          {key: '1', name: '刘安博'},
          {key: '2', name: '张土豪'},
          {key: '3', name: '李贫农'}
        ]
        setTimeout(() => {
          dispatch(Action.reloadData(customs))
          dispatch(Action.processingdata(false))
        }, 1000)
      }

      return returnValue
    }
}

可以看到,这是个“三级函数”,但是因为这个函数是redux内部调用的,所以我们不需要关心他的复杂度,
在上面的代码中,我已经获取到了action发送前后的状态,action的类型,所以你就根据action做你爱做的事情就可以了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容

  • 技术栈: react + redux + webpack + react-router + ES6/7/8 + i...
    黄昏少年阅读 3,077评论 0 19
  • 一、什么情况需要redux? 1、用户的使用方式复杂 2、不同身份的用户有不同的使用方式(比如普通用户和管...
    初晨的笔记阅读 2,028评论 0 11
  • 前言 本文 有配套视频,可以酌情观看。 文中内容因各人理解不同,可能会有所偏差,欢迎朋友们联系我讨论。 文中所有内...
    珍此良辰阅读 11,904评论 23 111
  • 本文将开始详细分析如何搭建一个React应用架构。 一. 前言 现在已经有很多脚手架工具,如create-reac...
    字节跳动技术团队阅读 4,325评论 1 23
  • 学习必备要点: 首先弄明白,Redux在使用React开发应用时,起到什么作用——状态集中管理 弄清楚Redux是...
    贺贺v5阅读 8,896评论 10 58