Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

简单实现一个 Redux 并在 React 中应用 #8

Open
bouquetrender opened this issue May 9, 2018 · 0 comments
Open

简单实现一个 Redux 并在 React 中应用 #8

bouquetrender opened this issue May 9, 2018 · 0 comments
Labels

Comments

@bouquetrender
Copy link
Owner

Redux 架构模式的基本概念其实并不太复杂,而 React-Redux 就是 Redux 架构在 React 中的实现。初识 Redux 是在刚开始学 React 的那段时间接触状态管理后,其目的就是为了解决共享数据的一些问题。由于一些大项目SPA非常复杂需要管理非常多的 state,只有在这时 Redux 才会真正会发挥它自身的用处。官方不建议在不了解一些状态管理库例如 Redux 的情况下使用 context。Redux 是解决方案之一,除此之外还有最近呼声高的 Mobx。下面两大部分内容许多解释都是看文档与教程后的理解,并不会对 Redux 一些概念进行太多解释。

Redux 简单实现

代码非常简略,以帮助理解 Redux 中一些基础概念。通过 create-react-app 脚手架快速配置好环境后,以下 JS 代码都写在 src/index.js 文件中。在此之前先在 index.html body中添加 <div id='title'></div><div id='content'></div> DOM结构。

渲染部分

DOM 初始化渲染部分,这里 renderTitle 与 renderContent 根据传入的值确定内容与文字颜色。而 renderApp 是负责总体渲染调用方法。往 renderApp 函数里传的newState 是取自 state,通过对比 new 与 oldstate 决定是否更新,先暂时不管 oldState 是个啥和这些 state 是如何获取到的。

function renderApp (newState, oldState = {}) {
  if(newState === oldState) return
  renderTitle(newState.title)
  renderContent(newState.content)
}
// 渲染标题
function renderTitle (title, oldTitle = {}) {
  if(title === oldTitle) return
  const titleDOM = document.getElementById('title')
  titleDOM.innerHTML = title.text
  titleDOM.style.color = title.color
}
// 渲染内容
function renderContent (content, oldContent = {}) {
  if(content === oldContent) return
  const contentDOM = document.getElementById('content')
  contentDOM.innerHTML = content.text
  contentDOM.style.color = content.color
}

Reducer

首先是关于一点纯函数的概念,在纯函数中函数返回结果只依赖于传入的参数而不是外部变量值,因为使用外部值后函数返回值变得不确定性。且纯函数执行过程中不存在修改传入值操作或者请求路由跳转等操作(通称副作用)。Reducer 正是一个纯函数,负责接收 action 处理 state 更新,而不是直接修改 state。更新方法有使用 ES6 提供的 Object.assign() 和对象展开运算符,展开运算符写法更优简洁明了。

function stateChange (state, action) {
  if(!state){
    return {
      title: { text: 'title here', color: 'red' },
      content: { text: 'contont here', color: 'blue'}
    }
  }
  switch (action.type) {
    case 'UPDATE_TITLE_TEXT':
      return { ...state, title: { ...state.title, text: action.text } }
    case 'UPDATE_TITLE_COLOR':
      return{ ...state, title: { ...state.title, color: action.color } }
    default:
      return state
  }
}

stateChange 方法就是一个十分简约的 Reducer ,当 state 为空时返回初始化的数据。根据接收的 action.type 执行相对应方法。

Action

action 是数据传入到 store 并执行相关处理的唯一一种载体途径。而 dispatch 就是将这种载体传入 store 的方法。下面代码通过 store 的 dispatch 方法将act对象传入了 store ,act对象就是一个 action,这样既可通过 store 调用 Reducer 并传入 action 从而执行 Reducer 里定义的相关操作。

const act = {type: 'UPDATE_TITLE_TEXT', text: 'title already change'}
store.dispatch(act)

Store

store 有点像中心枢纽的概念,将 actions 和 reducer 联系在一起,并且有以下基本方法:

  • 维护state
  • 提供获取state的方法
  • 提供dispatch方法
  • 通过subscribe注册监听器

下面代码是创建一个 store 的方法,代码最后向外暴露方法前自身 dispatch 一个空对象 action 用于初始化 state。

function createStore (reducer) {
  let state = null;
  const listeners = []
  const subscribe = (listener) => listeners.push(listener)
  const getState = () => state
  const dispatch = (action) => {
    state = reducer(state, action)
    listeners.map(fn => fn()) 
  }
  dispatch({}) 
  return { getState, dispatch, subscribe }
}

const store = createStore(stateChange)

可见 createStore 向外暴露了三个上面列举描述的方法,将定义的 Reducer(代码中的stateChange方法)传入 createStore 方法后就得到一个基本的 store。这里有一个观察者模式的简单应用,执行相关事件(dispatch)后发布所有方法(通过subscribe传入的自定义方法)。为何需要 subscribe 监听回调呢?代码如下:

const store = createStore(stateChange)

let oldState = store.getState()
store.subscribe(() => {
  const newState = store.getState()
  renderApp(newState, oldState)
  oldState = newState
})

当 state 更改后需要自动触发视图更新渲染,需要将渲染方法添加到发布事件里。并将新旧state传入渲染方法做对比决定是否进行视图渲染更新。通过以上几部分代码就完成一个十分基础简单的Redux架构,通过dispatch来改变title内容和颜色。Redux部分完整代码在这里

React-Redux 简单实现

为了更好的理解 React 里 Redux 工作模式,自己写一个超简单基础的 React-Redux 架构并应用还是需要的。同样利用 create-react-app 脚手架创建环境。完整代码地址可在Github上查看。总共分为五个部分。先粗略的说明各个文件的代码大致内容,主要逻辑是点击不同的按钮切换所有组件的文本颜色。

  • index.js 入口渲染,store创建
  • React-Redux.jsx 提供Provider容器组件 与 connect高阶组件
  • Header.jsx 头部内容组件
  • Content.jsx 信息内容组件
  • ThemeSwitch.jsx 整体颜色切换按钮组件

index.js

index.js完整代码。在入口部分,初始化createStore方法的代码与Reudx中一致,并且创建一个Reducer为themeReducer。

import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import Header from './Header'
import Content from './Content'
import { Provider } from './Reacr-Redux.jsx'

function createStore (reducer) {
  let state = null
  const listeners = []
  const subscribe = (listener) => listeners.push(listener)
  const getState = () => state
  const dispatch = (action) => {
    state = reducer(state, action)
    listeners.forEach((listener) => listener())
  }
  dispatch({})
  return { getState, dispatch, subscribe }
}

const themeReducer = (state, action) => {
  if (!state) return {
    themeColor: 'red'
  }
  switch (action.type) {
    case 'CHANGE_COLOR':
      return { ...state, themeColor: action.themeColor }
    default:
      return state
  }
}

const store = createStore(themeReducer)

index.js 下面这部分代码则为引入的 Header 与 Content 组件。Provider 容器组件引入自 React-Redux.js,Provider 的工作就是负责将传入的 store 存放到 context 里,这样子组件就可直接调用 store 提供的方法。

class Index extends Component {
  render () {
    return (
      <div>
        <Header />
        <Content />
      </div>
    )
  }
}

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

React-Redux.jsx

React-Redux.jsx 完整代码。主要是提供 Provider 容器组件与 connect 高阶组件。Provider 作用在 index.js 中已经得知。单单为了将传入的 store 存放到 context 里。这样包装组件就可直接调用 store 提供的方法。

export class Provider extends Component {
  static PropTypes = {
    store: PropTypes.object,
    children: PropTypes.any
  }
  static childContextTypes = {
    store: PropTypes.object
  }
  getChildContext() {
    return {
      store: this.props.store
    }
  }
  render() {
    return (
      <div>{this.props.children}</div>
    )
  }
}

而 connect 高阶组件目的就是为了将调用 store、获取更新 state 方法、dispatch 方法以及 subscribe 添加 进行了抽取。下面会详细的解释。

// 高阶组件 connect 
export const connect = (mapStateToProps, mapDispatchProps) => (WrappedComponent) => {
  class Connect extends Component {
    static contextTypes = { store: PropTypes.object }

    constructor (props) {
      super(props)
      this.state = { allProps: {} }
    }
    componentWillMount() {
      const { store } = this.context
      this._updateState()
      store.subscribe(() => this._updateState())
    }
    _updateState() {
      const {store} = this.context
      let stateProps = mapStateToProps ? mapStateToProps(store.getState(), this.props) : {}
      let dispatchProps = mapDispatchProps  ? mapDispatchProps(store.dispatch, this.props): {}
      this.setState({
        allProps: {...stateProps, ...dispatchProps, ...this.props }
      })
    }
    render () {
      return <WrappedComponent {...this.state.allProps} />
    }
  }
  return Connect
}

componentWillMount 中,由于 Provider 在组件根位置,所以高阶函数中可进行 this.context 获取 store。同时执行 _updateState 方法,将 store 中的 state 传入 mapStateToProps,dispatch 方法传入 mapDispatchProps。返回值作为 props 传入原组件中。这样进行高阶函数包装后的组件就可进行相关 state 读取和 dispatch 操作。mapStateToProps 与 mapDispatchProps 还可以接收外界传给组件的 props 然后在函数内部进行处理。

Content.jsx / Header.jsx

header 与 content 组件结构基本一致展示内容用。render 内容都是单单一个 div 加上一个颜色样式。

import React, { Component, PropTypes } from 'react';
import {connect} from './Reacr-Redux'

class Header extends Component {
  static propTypes = {
    themeColor: PropTypes.string
  }
  render () {
    return (
      <h1 style={{ color: this.props.themeColor }}>Header Content</h1>
    )
  }
}
const mapStateToProps = (state) => {
  return { themeColor: state.themeColor }
}
Header = connect(mapStateToProps)(Header)

mapStateToProps 和 mapDispatchProps 通常都在包装函数中进行定义然后传给高阶函数。这里 mapStateToProps 接收的 state 则是高阶组件中的 store.getState(),这样就可通过 state.themeColor 获取到值并返回到高阶组件定义的 state 中,并作为 props 传给包装函数。

ThemeSwitch.jsx

ThemeSwitch.jsx 完整代码,关键的调用 dispatch 修改 color 值是在 ThemeSwitch 组件里。

class ThemeSwitch extends React.Component {
  handleSwitchColor(color) {
    if (this.props.onSwitchColor) {
      this.props.onSwitchColor(color)
    }
  }
  render () {
    return (
      <div>
        <button style={{ color: this.props.themeColor }} onClick={this.handleSwitchColor.bind(this, 'red')}>Red</button>
        <button style={{ color: this.props.themeColor }} onClick={this.handleSwitchColor.bind(this, 'blue')}>Blue</button>
      </div>
    );
  }
}
const mapStateToProps = (state) => {
  return { themeColor: state.themeColor }
}
const mapDispatchToProps = (dispatch) => {
  return {
    onSwitchColor: (color) => {
      dispatch({type: 'CHANGE_COLOR', themeColor: color})
    }
  }
}
ThemeSwitch = connect(mapStateToProps, mapDispatchToProps)(ThemeSwitch)

和 mapStateToProps 返回的 state 一样,mapDispatchToProps 返回的对象同样被 connect 当作是 props 参数传给被包装组件,对象内部的方法通常是接收需要修改的参数,并调用dispatch执行修改。整个流程就是这样,传入 mapStateToProps 与 mapDispatchToProps 说明需要的 state 与需要执行的 dispatch,通过 connect 高阶函数后的包装函数就可对 state 进行相关获取和操作。这样一个非常简单的 Redux 结构就应用到了 React 中,完整代码地址可在Github上查看。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant