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 (
      
        {this.props.counter.count}
        
         { reset(0) }}>
          归零
        
        
          加1
        
        
          减1
        
      

    )
  }
}

一个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 (
      
    )
  }
};

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

(三)redux难题

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

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

  1. 映射方法的时候,定义一个key
    如果我们不想污染this.props,像state一样可以通过this.props..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: (//导航栏按钮
        
          
            
          
          
            
          
        

      )
    }
  };
  render () {
    return (
      
    )
  }
};

  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)