React个人入门总结《五》

简介

这一次总结的是 React-redux 的实现,可以参考一下 大佬的文章

首先要知道 redux 的基本使用:

  1. 创建一个 Store

    <!-- store -->
    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({}) // 初始化 state
      return { getState, dispatch, subscribe }
    }
    
    <!-- reducer -->
    const themeReducer = (state = {}, action) => {
      switch (action.type) {
        case 'CHANGE_COLOR':
          return { ...state, themeColor: action.themeColor }
        default:
          return state
      }
    }
    
    <!-- 创建 store -->
    const store = createStore(themeReducer)
    复制代码

    Store 是保存数据的地方,整个应用只有一个,调用 CreateStore 函数并且传入一个 Reducer 来创建一个 Store,并且会返回新的 Store 对象。

  2. 获取当前的 State

    <!-- 调用 store.getState 获取当前的状态 -->
    const state = store.getState()
    复制代码

    StateStore 里面包含的数据对象,可以通过 Store.getState() 获取

  3. 通过 Dispatch 发送 Action 改变 State

    <!-- 调用 dispatch 发送 action -->
    store.dispatch({
        type: 'CHANGE_COLOR',
        themeColor: 'blue'
    })
    复制代码

    Action 就是 View 发出的通知,表示 View 要变化,其中 Type 是必须的,其余可以 自定义

    如果要写多个 Action 觉得麻烦,可以使用 Action Creator 函数来生产 Action

    function updateThemeColor (action) {
        type: action.type,
        themeColor: action.themeColor
    }
    
    store.dispatch( updateThemeColor({ type: 'CHANGE_COLOR', themeColor: 'blue' }) )
    复制代码
  4. ReducerStore 收到 Action 之后用来计算 State 并且返回新的 State,也就是说必须要有 Return

    <!-- reducer -->
    
    <!-- 初始 state 是必须的,redux 规定不能为 undefined 和 null -->
    const themeReducer = (state = {}, action) => {
      switch (action.type) {
        case 'CHANGE_COLOR':
          return { ...state, themeColor: action.themeColor }
        default:
          return state
      }
    }
    复制代码

    Reducer 可以根据不同的 Type 来进行不同的逻辑处理,并且每次都会返回新的 state 来覆盖原来的 state

    Reducer 是一个纯函数,同样的输入就会得到同样的输出。

    Reducer 必须要返回一个新的状态,而不是改变原有的状态,请参考下面写法:

    // State 是一个对象
    function reducer(state, action) {
      return Object.assign({}, state, { thingToChange });
      // 或者
      return { ...state, ...newState };
    }
    
    // State 是一个数组
    function reducer(state, action) {
      return [...state, newItem];
    }
    复制代码
  5. 调用 subscribe 传入一个函数,状态改变时会调用此函数。

    store.subscribe(()=> {
        ReactDOM.render()
    })
    复制代码

    Store.subscribe 方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。

    一般传入 renderthis.setState() 来监听页面重新渲染。

    调用此方法会返回一个函数,调用函数之后可以解除监听。


React-redux

首先之前向组件传递参数时,第一次使用的是 状态提升,即通过父级传入一个函数然后拿到组件里面的东西,再传入另一个组件里面。

当嵌套的太多层时,使用 状态提升 会非常麻烦,然后第二次就开始使用了 Context ,由于 Context 能随意被改变,这时我们可以把 ContextStore 结合使用,这样就不能随意的改变 Context ,并且状态还能共享。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
// 引入组件
import { Header } from './component/Header';
import { Content } from './component/Content';
import * as serviceWorker from './serviceWorker';
<!-- store -->
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 };
}
<!-- reducer -->
const themeReducer = (state, action) => {
  if (!state) return {
    themeColor: 'red'
  }
  switch (action.type) {
    case 'CHANGE_COLOR':
      return { ...state, themeColor: action.themeColor }
    default:
      return state
  }
}
<!-- 创建 store -->
const store = createStore(themeReducer);


class Index extends Component {
  <!-- 设置子组件的 contextType -->
  static childContextTypes = {
    store: PropTypes.object
  }
  <!-- 设置 context -->
  getChildContext() {
    return { store }
  }

  render() {
    return (
      <div className="index">
        <Header />
        <Content />
      </div>
    )
  }
}

ReactDOM.render(<Index />, document.getElementById('root'));
复制代码

创建 store 然后把它放到 context 里面,这样所有子组件都可以拿到了。

<!-- Header -->
import React, { Component } from 'react';
import PropTypes from 'prop-types';

class Header extends Component {
  static contextTypes = {
   store: PropTypes.object 
  }

  constructor(props) {
    super(props);
    this.state = {
      themeColor: ''
    }
  }

  componentWillMount() {
    let { store } = this.context;
    this._updateThemeColor();
    store.subscribe(() => this._updateThemeColor())
  }

  _updateThemeColor() {
    let state = this.context.store.getState();
    this.setState({
      themeColor: state.themeColor
    })
  }

  render() {
    return (
      <div className="header">
        <h1 style={{color: this.state.themeColor}}>is header</h1>
      </div>
    )
  }
} 
export { Header };

<!-- Content-->
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { ThemeSwitch } from './ThemeSwitch';

class Content extends Component {
  static contextTypes = {
    store: PropTypes.object
  }

  componentWillMount() {
    let { store } = this.context;
    this._updateThemeColor();
    store.subscribe(() => this._updateThemeColor())
  }

  _updateThemeColor() {
    let state = this.context.store.getState();
    this.setState({
      themeColor: state.themeColor
    })
  }

  render() {
    return (
      <div className="header">
        <h2 style={{ color: this.state.themeColor }}>is Content</h2>
        <ThemeSwitch />
      </div>
    )
  }
}
export { Content };

<!-- ThemeSwitch -->
import React, { Component } from 'react';
import PropTypes from 'prop-types';

class ThemeSwitch extends Component {
  static contextTypes = {
    store: PropTypes.object
  }

  componentWillMount() {
    let { store } = this.context;
    this._updateThemeColor();
    store.subscribe(() => this._updateThemeColor())
  }
  
  _updateThemeColor() {
    let state = this.context.store.getState();
    this.setState({
      themeColor: state.themeColor
    })
  }
  
  render() {
    return (
      <div className="header">
        <button style={{ color: this.state.themeColor }}>red</button>
        <button style={{ color: this.state.themeColor }}>blue</button>
      </div>
    )
  }
}

export { ThemeSwitch };
复制代码

上面三个子组件使用 store.getState() 获取到 reducer 设置的默认状态,这样的话就可以实现共享状态了。

接下来我们实现点击按钮改变颜色:

<!-- ThemeSwitch -->
updateThemeColor(color) {
    let { store } = this.context;
    store.dispatch({
      type: 'CHANGE_COLOR',
      themeColor: color
    })
}

render() {
    return (
      <div className="header">
        <button style={{ color: this.state.themeColor }} onClick={this.updateThemeColor.bind(this, 'red')}>red</button>
        <button style={{ color: this.state.themeColor }} onClick={this.updateThemeColor.bind(this, 'blue')}>blue</button>
      </div>
    )
}
复制代码

调用 dispatch 然后传入 action ,然后会调用 reducer 函数,然后根据传入的 action.type 改变状态,之后再返回一个新的状态。

返回新状态时要想监听页面的更新,可以在 subscribe 传入要监听的函数,这样就可以在调用 dispatch 同时会调用你传入的函数,然后再一次调用 this.setState 触发页面重新渲染。

  _updateThemeColor() {
    <!-- 重新获取一次状态 -->
    let state = this.context.store.getState();
    <!-- 重新设置,并且触发重新渲染 -->
    this.setState({
      themeColor: state.themeColor
    })
  }
  
  componentWillMount() {
    let { store } = this.context;
    <!-- 首次渲染 -->
    this._updateThemeColor();
    store.subscribe(() => this._updateThemeColor())
  }
复制代码

connect

上面的组件有着重复的逻辑,首先取出 storestate 然后设置成自己的状态,还有一个就是对 context 依赖过强,这时我们可以利用 高阶组件 来和 context 打交道,这时就不用每个组件都获取一遍 store 了。

import React, { Component } from 'react';
import PropTypes from 'prop-types';
<!-- 接受一个组件 -->
const connect = (WrappedComponent) => {
  class Connect extends Component {
    static contextTypes = {
      store: PropTypes.object
    }
    
    render() {
      const { store } = this.context;
      return <WrappedComponent />
    }
  }
  return Connect;
}
export { connect };
复制代码

connect 是用于从 UI 组件生成 容器组件 ,也就是说我们传入的组件只是负责呈现和展示,而 容器组件 负责业务逻辑和带有内部状态,connect 负责的是将两者合并起来,生成并返回新的组件。

由于每个传进去的组件需要的 store 里面的数据都不一样,所以我们还要传入一个函数来告诉 高阶组件 正确获取数据。

  • mapStateToProps
const mapStateToProps = (state) {
    return {
        themeColor: state.themeColor
    }
}
复制代码

mapStateToProps 是一个获取 store 保存的状态,然后将这个状态转化为 UI组件 的数据的函数,它必须要返回一个对象,而这个对象用来进行状态转化的。

import React, { Component } from 'react';
import PropTypes from 'prop-types';

const connect = (mapStateToProps) => (WrappedComponent) => {
  class Connect extends Component {
    static contextTypes = {
      store: PropTypes.object
    }

    render () {
      const { store } = this.context;
      <!-- 从store获取state并且转化之后全部传入 props -->
      let stateProps = mapStateToProps(store.getState());
      return <WrappedComponent {...stateProps} />
    }
  }
  return Connect;
}

export { connect };
复制代码

上面最关键的一步就是调用 mapStateToProps 时,从 store 获取到 state 之后然后传入到 mapStateToProps 函数中,然后这函数会返回一个转化后的 state ,然后把这些转化的状态全部传入 props 里面。

可以看出 connectDumb组件(纯组件)context 连起来了,下面只需要调用 connect 然后传入一个 mapStateToPropsUI组件 就可以使用了。

<!-- Header -->
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from './React-redux';

class Header extends Component {
  static propTypes = {
    themeColor: PropTypes.string
  }

  render() {
    return (
      <div className="header">
        <h1 style={{color: this.props.themeColor}}>is header</h1>
      </div>
    )
  }
} 

const mapStateToProps = (state) => {
  return {
    themeColor: state.themeColor
  }
}

Header = connect(mapStateToProps)(Header)
export { Header };

<!-- Content -->
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from './React-redux';
import { ThemeSwitch } from './ThemeSwitch';

class Content extends Component {
  
  static propTypes = {
    themeColor: PropTypes.string
  }

  render() {
    return (
      <div className="header">
        <h2 style={{ color: this.props.themeColor }}>is Content</h2>
        <ThemeSwitch />
      </div>
    )
  }
}

const mapStateToProps = (state) => {
  return {
    themeColor: state.themeColor
  }
}

Content = connect(mapStateToProps)(Content)
export { Content };
复制代码

由于 mapStateToProps 返回的对象经过 connect 传入组件的 props 中,我们直接可以用 this.props 直接获取到。

接着把 connect 的代码复制到一个叫 React-redux 的文件,然后可以删掉之前那些引入 store 的代码了。

现在点击按钮只有按钮会变颜色,接下来我们修改一下 connect

import React, { Component } from 'react';
import PropTypes from 'prop-types';

const connect = (mapStateToProps) => (WrappedComponent) => {
  class Connect extends Component {

    static contextTypes = {
      store: PropTypes.object
    }

    constructor (props) {
      super(props);
      this.state = { 
        allProps: {}
      }
    }
    
    componentWillMount () {
      const { store } = this.context;
      this._updateProps();
      store.subscribe(() => this._updateProps())
    }

    _updateProps () {
      const { store } = this.context
      <!-- 现在 mapStateToProps 可以接受两个参数 -->
      let stateProps = mapStateToProps(store.getState(), this.props)
      <!-- 整合普通的 props 和从 state 生成的 props -->
      this.setState({
        allProps: {
          ...stateProps,
          ...this.props
        }
      })
    }

    render () {
      return <WrappedComponent { ...this.state.allProps } />
    }
  }
  
  return Connect;
}
export { connect };
复制代码

每次点击按钮调用 dispatch 都会把心的 state 设置到自己的 state 之后,然后返回给组件,这样组件之前的 props 也会保留,同时 mapStateToProps 可以接受第二个参数,这个参数为当前 UI组件props

<!-- 第一个为 store 获取到的 state , 第二个为当前 ui 组件的 props (不是最新的) -->
const mapStateToProps = (state, props) => {
  console.log(state, props)
  return {
    themeColor: state.themeColor
  }
}
复制代码

使用 props 作为参数后,如果容器组件的参数发生变化,也会引发 UI组件 重新渲染,connect 方法可以省略 mapStateToProps 参数,这样 store 的更新不会引起组件的更新。

  • mapDispatchToProps
const mapDispatchToProps = (dispatch, props) => {
    return {
        updateThemeColor: () => {
            dispatch({
                type: 'CHANGE_COLOR',
                payload: ''
            })
        }
    }
}
复制代码

mapDispatchToPropsconnect 的第二个参数,用来建立 UI组件 的参数到 store.dispatch 方法的映射,它作为函数时可以接受两个参数,一个是 dispatch ,一个则是 UI组件props

mapDispatchToProps 可以定义 action 然后传给 store

import React, { Component } from 'react';
import PropTypes from 'prop-types';

const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
  class Connect extends Component {
    static contextTypes = {
      store: PropTypes.object
    }

    constructor (props) {
      super(props);
      this.state = { 
        allProps: {}
      }
    }
    
    componentWillMount () {
      const { store } = this.context;
      this._updateProps();
      store.subscribe(() => this._updateProps())
    }

    _updateProps () {
      const { store } = this.context
      let stateProps = mapStateToProps
        ? mapStateToProps(store.getState(), this.props)
        : {}
      let dispatchProps = mapDispatchToProps
        ? mapDispatchToProps(store.dispatch, this.props)
        : {}
      this.setState({
        allProps: {
          ...stateProps,
          ...dispatchProps,
          ...this.props
        }
      })
    }
    render () {
      return <WrappedComponent { ...this.state.allProps } />
    }
  }
  return Connect;
}
export { connect };
复制代码

接受 mapDispatchToProps 作第二个参数,调用时把 dispatchprops 传进去,返回 onClickUpdate 然后直接传入 props 中返回给 UI组件 ,接着我们可以直接调用 this.props.onClickUpdate 然后调用 dispatch 来更新状态。

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from './React-redux';

class ThemeSwitch extends Component {
  static contextTypes = {
    onClickUpdate: PropTypes.func
  }
  <!-- 点击调用 onClickUpdate -->
  updateThemeColor(color) {
    if(this.props.onClickUpdate) {
      this.props.onClickUpdate(color)
    }
  }

  render() {
    return (
      <div className="header">      
        <button style={{ color: this.props.themeColor }} onClick={this.updateThemeColor.bind(this, 'red')}>red</button>
        <button style={{ color: this.props.themeColor }} onClick={this.updateThemeColor.bind(this, 'blue')}>blue</button>
      </div>
    )
  }
}
<!-- 在真正的 react-redux 不一定是函数 -->
const mapStateToProps = (state, props) => {
  return {
    themeColor: state.themeColor
  }
}
<!-- 在真正的 react-redux 可以是一个对象 -->
const mapDispatchToProps = (dispatch, props) => {
  return {
    onClickUpdate: (color) => {
      dispatch({
        type: 'CHANGE_COLOR',
        themeColor: color
      })
    }
  }
}

ThemeSwitch = connect(mapStateToProps, mapDispatchToProps)(ThemeSwitch);
export { ThemeSwitch };
复制代码

这样点击按钮之后又可以改变颜色了。

Provider

connect 方法生成容器后需要拿到 state 对象,目前咱们能拿到 store 是因为在 index.js 中设置了 context ,这样会直接污染 index.jsReact-redux 提供了 Provider 来充当最外层容器,这样就不需要在 index 设置 context 了。

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>
    )
  }
}

export { Provider };
复制代码

React-redux 的文件增加上面的代码,其实也就是另外设置一个容器来替代之前 index.js 干的活,这里返回了 this.props.children ,也说明要用这个组件把其他的组件包起来。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import { Provider } from './component/React-redux';
// 引入组件
import { Header } from './component/Header';
import { Content } from './component/Content';
import * as serviceWorker from './serviceWorker';

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);

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


ReactDOM.render(
  <!-- 把 store 和 外层组件包起来 -->
  <Provider store= { store }>
    <Index />
  </Provider>, 
  document.getElementById('root')
);
复制代码

Store 传入给 Provider ,然后它把 store 设置成 context ,这样其他子组件都能拿到 store ,并且把最外层容器包起来,然后使用 this.props.children 全部罗列出来。

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from './React-redux';

class Header extends Component {
  <!-- 别忘了声明这玩意,不然拿不到 -->
  static contextTypes = {
    store: PropTypes.object
  }

  static propTypes = {
    themeColor: PropTypes.string
  }

  componentWillMount() {
    console.log(this.context)
  }

  render() {
    return (
      <div className="header">
        <h1 style={{color: this.props.themeColor}}>is header</h1>
      </div>
    )
  }
} 

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

Header = connect(mapStateToProps)(Header)
export { Header };
复制代码

拿到之后接下来就可以浪了,可以在当前组件调用里面的方法,非常灵活。

总结

  1. 首先在 index.js 引入创建好的 store ,然后引入 Providerindex 包起来,并且给它传递 store
  2. 如果页面需要拿到状态直接调用 store.getState ,如果想监听函数调用 store.subscribe 传入函数。
  3. 如果想订阅 store 或者修改 state ,在当前组件引入 connect 接着传入 mapStateToPropsmapDispatchToProps 来呈现新的 UI组件
  4. dispatch 还可以拓展,真正的 react-redux 还可以使用中间件实现异步 action ,如需要从后台返回的状态来改变当前的 state 类似这种操作。
  5. 可以使用 combineReducers 管理多个 reducer,一个 store 管理N种状态。

上一篇 --- React个人入门总结《四》
下一篇 --- React个人入门总结《六》
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值