RN学习笔记2-Redux

作者: Realank | 来源:发表于2018-07-10 15:46 被阅读131次

    一、背景

    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做你爱做的事情就可以了。

    相关文章

      网友评论

      本文标题:RN学习笔记2-Redux

      本文链接:https://www.haomeiwen.com/subject/zhnfpftx.html