react-redux 学习以及模块化配置方案

rudux

redux 运行流程图:

简单概述:click -> store.dispatch(action) -> reduer -> newState -> viewUpdate

react-readux 中 通过 connect 链接组件和 redux , this.props.dispatch() 调用

后面将会讲到...

redux 依赖包也是十分的简洁

先来个 demo

const redux = require('redux')
const createStore = redux.createStore

const types = {
  UPDATE_NAME: 'UPDATE_NAME'
}

const defaultStore = {
  user: 'tom'
}

/**
 * reducer 纯函数 接收一个state,返回一个新的state
 * @param {Object} state
 * @param {Object} action [type] 必选参数
 * @return newState
 * */
function getUser(state = defaultStore, action) {
  const { type, payload } = action
  let res = Object.assign({}, defaultStore)
  switch (type) {
    case types.UPDATE_NAME:
      res.user = payload.name
      break
    default:
      return res
  }
  return res
}

const store = createStore(getUser)

/**
 * listener
 * */
store.subscribe(() => {
  console.log(store.getState())
})

/**
 * dispatch(action) action
 * */
store.dispatch({
  type: types.UPDATE_NAME,
  payload: {
    name: '大帅哥'
  }
})
//@log { name: '大帅哥' }
复制代码
  1. 用户发出 actionstore.dispatch(action)
  2. Store 自动调用 Reducer , 返回新的 statelet nextState = getUser(previousState, action)
  3. State 一旦有变化,Store 就会调用监听函数 【store.subscribe(listener)

运行过程如下:

store

Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store 常用方法:

  • store.dispatch() :分发 action 较为常用
  • store.subscribe() : state 发生变化后立即执行
  • store.getState() : 获取 store 中存着的 state

createStore

createStore 如其名,创建 store 下面是该方法的部分源码:

/**
 * @param {Function} reducer 函数
 * @param {any} [preloadedState] The initial state
 * @param {Function} [enhancer] The store enhancer
 * @returns {Store}
 * */
export default function createStore(reducer, preloadedState, enhancer) {
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }
  // ...
  return {
    dispatch, // 分发 action
    subscribe, // 监听器
    getState, // 获取 store 的 state 值
    replaceReducer,
    [$$observable]: observable // 供Redux内部使用
  }
}
复制代码
  • preloadedState: 初始化的initialState,第二个参数不是Object,而是FunctioncreateStore会认为你忽略了preloadedState而传入了一个enhancer
  • createStore会返回enhancer(createStore)(reducer, preloadedState)的调用结果,这是常见高阶函数的调用方式。在这个调用中enhancer接受createStore作为参数,对createStore的能力进行增强,并返回增强后的createStore

dispatch(action)

diapatch 是 store 对象的方法,主要用来分发 action ,

redux 规定 action 一定要包含一个 type 属性,且 type 属性也要唯一

dispatch 是 store 非常核心的一个方法,也是我们在应用中最常使用的方法,下面是 dispatch 的源码 :

function dispatch(action) {
  if (!isPlainObject(action)) {
    // 校验了action是否为一个原生js对象
    throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.')
  }

  if (typeof action.type === 'undefined') {
    // action对象是否包含了必要的type字段
    throw new Error('Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?')
  }

  if (isDispatching) {
    // 判断当前是否处于某个action分发过程中, 主要是为了避免在reducer中分发action
    throw new Error('Reducers may not dispatch actions.')
  }

  try {
    isDispatching = true
    currentState = currentReducer(currentState, action)
  } finally {
    isDispatching = false
  }

  const listeners = (currentListeners = nextListeners)
  for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i]
    listener()
  }
  // 在一系列检查完毕后,若均没有问题,将当前的状态和action传给当前reducer,用于生成新的state
  return action
}
复制代码

reducer && store.replaceReducer

Redux 中负责响应 action 并修改数据的角色就是reducerreducer的本质实际上是一个函数 replaceReducer:

/**
 * @desc 替换当前的reducer的函数
 * @param {Function}
 * @return {void}
 */
function replaceReducer(nextReducer) {
  if (typeof nextReducer !== 'function') {
    throw new Error('Expected the nextReducer to be a function.')
  }

  currentReducer = nextReducer
  dispatch({ type: ActionTypes.REPLACE })
}
复制代码

replaceReducer 使用场景:

  • 当你的程序要进行代码分割的时候
  • 当你要动态的加载不同的 reducer 的时候
  • 当你要实现一个实时 reloading 机制的时候

中间件 middleware

以上介绍了 redux 的实现流的过程,应用场景无非于

button -- click --> disptch -- action --> reducer -- newState --> view

但是这种实现方式是基于同步的方式的,日常开发中当然少不了 http 这些异步请求,这种情况下必须等到服务器数据返回后才重新渲染 view, 显然某些时候回阻塞页面的展示。

举例来说,要添加日志功能,把 ActionState 打印出来,可以对 store.dispatch 进行如下改造。

let next = store.dispatch
store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action)
  next(action)
  console.log('next state', store.getState())
}
复制代码

上面代码中,对 store.dispatch 进行了重定义,在发送 Action 前后添加了打印功能。这就是中间件的雏形。

中间件就是一个函数,对 store.dispatch 方法进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。

applyMiddleware

Redux 提供了applyMiddleware来装载middleware: 它是 Redux 的原生方法,**作用是将所有中间件组成一个数组,依次执行。**下面是它的源码。

/**
 * @param {...Function} middlewares
 * returns {Function} A store enhancer applying the middleware
 */
export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}
复制代码

所有中间件被放进了一个数组 chain,然后嵌套执行,最后执行 store.dispatch。可以看到,中间件内部(middlewareAPI)可以拿到getStatedispatch这两个方法

compose 实际上是函数式编程中的组合,接收多个函数体并且将其组合成一个新的函数,例如compose 后 [fn1, fn2...] 依次从右到左嵌套执行函数 而compose用于applyMiddleware 也是为了组合中间件 dispatch = compose(...chain)(store.dispatch) ==> dispatch=fn1(fn2(fn3(store.dispatch)))

/**
 * @param {...Function} funcs The functions to compose.
 * @returns {Function} A function obtained by composing the argument functions
 */
export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
复制代码

redux-thunk

上面的中间件的介绍可以知道 redux 通过 applyMiddleware 来装载中间件,通过 compose 方法可以组合函数

异步的问题可以通过 redux-thunk 解决,用法也不难 react 组件中使用相关如下:

// 配置 redux 加上这个...
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
// ...
const store = createStore(getUser, compose(applyMiddleware(thunk)))

// react 中使用
import { connect } from 'react-redux'

handleClick = () => {
  this.props.dispatch(dispatch => {
    return axios.get('https://randomuser.me/api/').then(res => {
      dispatch({
        type: types.CHANGE_ARRAY,
        payload: {
          name: res.data.results[0].name.title
        }
      })
    })
  })
}

const mapStateToProps = (state, props) => {
  return {
    name: state.demo.name
  }
}

export default connect(mapStateToProps)(Demo)
复制代码

处理异步的还有很多插件 如 redux-soga 等,楼主并未实践过,所以不做延伸...

react-redux

下面是在 react 中使用的代码的雏形:

import { createStore } from 'redux'

let defaultState = {
  count: 1
}

/**
 * Reducer
 * */
function demoReducer(state = defaultState, action = {}) {
  const { type, payload } = action
  const res = Object.assign({}, state)
  if (type === 'changeCount') {
    res.count = payload.count
  }
  return res
}

/**
 * @Store 存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。
 * combineReducers({ ...reducers }) 可以组合多个reducer
 * */
const store = createStore(
  demoReducer,
  window.devToolsExtension && window.devToolsExtension() // 配置redux 开发工具
)

// ... 根元素下配置下 Provider
import { Provider } from 'react-redux'

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

// 组件中使用
import { connect } from 'react-redux'

//use
this.dispatch({
  type: 'changeCount',
  payload: {
    count: 22
  }
})

const mapStateToProps = (state, props) => {
  return {
    name: state.demo.name
  }
}

export default connect(mapStateToProps)(Demo)
复制代码

mapStateToProps

  • 用于建立组件跟 store 的 state 的映射关系作为一个函数,它可以传入两个参数,结果一定要返回一个 object
  • 传入mapStateToProps之后,会订阅 store 的状态改变,在每次 store 的 state 发生变化的时候,都会被调用
  • 如果写了第二个参数 props,那么当 props 发生变化的时候,mapStateToProps 也会被调用

mapDispatchToProps

  • mapDispatchToProps用于建立组件跟 store.dispatch 的映射关系
  • 可以是一个 object,也可以传入函数
  • 如果mapDispatchToProps是一个函数,它可以传入 dispatch,props,定义 UI 组件如何发出 action,实际上就是要调用 dispatch 这个方法
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'

// 页面中使用...
this.props.changeName()

const mapDispatchToProps = ({ changeName } = (dispatch, props) => {
  return bindActionCreators(
    {
      changeName: function() {
        return {
          type: types.UPDATE_NAME,
          payload: {
            name: '大大大'
          }
        }
      }
    },
    dispatch
  )
})

export default connect(mapDispatchToProps)(App)
复制代码

模块化配置

下面的配置仅供参考。实现的功能:

  • 整合 actiontypesreducer 到一个文件
  • 根据开发/生成环境配置不同的 redux 中间件(开发环境配置 dev-tools )
  • 支持装饰器模式
  • redux 热加载配置(这里面顺便将 react 热加载配置也加上了)

注意:项目基于 create-react-app eject 后的配置改造实现的。下面用了别名 @ ,需要改下 webpack 的配置,如果你配置不成功。详情可以看我的 github 上面有源码. 链接入口

安装

npm install redux react-redux redux-thunk --save
npm install redux-devtools-extension react-hot-loader -D
npm install @babel/plugin-proposal-decorators -D
复制代码

相关文件夹如图:

models/demo.js

demo 模块。

// types
const ADD_COUNT = 'ADD_COUNT'

// actions
export const addCount = () => {
  return { type: ADD_COUNT }
}

// state
const defaultState = {
  count: 11
}

// reducer
export const demoReducer = (state = defaultState, action) => {
  switch (action.type) {
    case ADD_COUNT:
      return { ...state, count: ++state.count }
    default:
      return state
  }
}

export default demoReducer
复制代码
models/index.js

模块的导出口。

import { combineReducers } from 'redux'

import demo from './demo'

export default combineReducers({
  demo
})
复制代码
redux/index.js

redux 仓库的总出口

import thunk from 'redux-thunk'
import { compose, createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'

import rootReducer from './models'

let storeEnhancers
if (process.env.NODE_ENV === 'production') {
  storeEnhancers = compose(thunk)
} else {
  storeEnhancers = compose(composeWithDevTools(applyMiddleware(thunk)))
}

const configureStore = (initialState = {}) => {
  const store = createStore(rootReducer, initialState, storeEnhancers)

  if (module.hot && process.env.NODE_ENV !== 'production') {
    // Enable Webpack hot module replacement for reducers
    module.hot.accept('./models', () => {
      console.log('replacing reducer...')
      const nextRootReducer = require('./models').default
      store.replaceReducer(nextRootReducer)
    })
  }

  return store
}

export default configureStore()
复制代码
src/index.js

react 项目的入口配置。

import React from 'react'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'
import App from './App'
import { Provider } from 'react-redux'
import store from '@/redux'

const render = Component => {
  ReactDOM.render(
    <AppContainer>
      <Provider store={store}>
        <Component />
      </Provider>
    </AppContainer>,
    document.getElementById('root')
  )
}

render(App)

if (module.hot) {
  module.hot.accept('./App', () => {
    render(App)
  })
}
复制代码
App.jsx
import React, { Component, Fragment } from 'react'
import { connect } from 'react-redux'
import { addCount } from '@/redux/models/demo'
import { Button } from 'antd'

const mapStateToProps = state => ({
  count: state.demo.count
})

@connect(
  mapStateToProps,
  { addCount }
)
class ReduxTest extends Component {
  render() {
    return (
      <Fragment>
        {this.props.count}
        <Button type="primary" onClick={this.props.addCount}>
          Click
        </Button>
        <hr />
      </Fragment>
    )
  }
}

export default ReduxTest
复制代码
.babelrc

配置 babel 装饰器模式

{
  "presets": ["react-app"],
  "plugins": [
    ["@babel/plugin-proposal-decorators", { "legacy": true }]
  ]
}
复制代码

vscode 装饰器模式如果有报警的话,可以根目录下新建 jsconfig.json

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "baseUrl": "./",
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
    "jsx": "react"
  },
  "exclude": [
    "node_modules",
    "build",
    "config",
    "scripts"
  ]
}
复制代码

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
前端开发中的React Redux全家桶是一套常用的技术栈,用于构建复杂的Web应用程序。React是一个由Facebook开发的JavaScript库,用于构建用户界面。它通过将应用程序拆分成可重用的组件,使开发人员能够更轻松地开发、测试和维护Web应用程序。 Redux是一个用于管理应用程序状态的库。它采用了一种称为单一状态树的模式,将整个应用程序的状态存储在一个对象中,并使用纯粹的函数来修改状态。Redux的核心概念包括:store、reducer和action。Store是应用程序的状态容器,reducer是一个纯函数,用于根据action来修改状态,而action代表用户触发的操作。 React Redux是将React和Redux结合在一起使用的库。通过使用React Redux,我们可以将Redux的状态管理功能集成到React组件中。React Redux提供了一种称为容器组件的机制,它负责从Redux store中提取数据,并将其作为props传递给展示组件。这种分离开发的模式使得代码更加模块化和易于维护。 React Redux全家桶还包括一些其他的辅助库,如React Router用于跟踪和管理应用程序的URL路径,以及Redux Thunk或Redux Saga用于处理异步操作。这些库的整合和使用能够帮助开发人员构建可扩展、高效和易于维护的前端应用程序。 总之,前端开发中的React Redux全家桶提供了一套完善的工具和库,帮助开发人员构建复杂的Web应用程序。它能够将状态管理和用户界面开发结合在一起,并提供了一种模块化和分离开发的解决方案。通过学习和使用React Redux全家桶,开发人员可以提高开发效率,并构建出更好的用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值