react进阶-redux

个人博客地址

react进阶-redux

开始

Redux,一种新型的前端“架构模式”。经常和 React.js 一并提出,你要用 React.js 基本都要伴随着 ReduxReact.js 结合的库 React-redux

要注意的是,ReduxReact-redux 并不是同一个东西。Redux是一种架构模式(Flux 架构的一种变种),它不关注你到底用什么库,你可以把它应用到 ReactVue,甚至跟 jQuery结合都没有问题。而 React-redux 就是把 Redux这种架构模式和 React.js 结合起来的一个库,就是 Redux架构在 React.js 中的体现。

安装reduxreact-redux
npm i -S redux react-redux

那么如何去使用它们? 首先得了解provider组件,和connect以及store,action,reducer.

解析 redux

redux中含有store,action,reducer.redux的作用是什么? 它们是如何工作的.

redux的作用就是用来管理数据状态的.

随着 JavaScript 单页应用开发日趋复杂,JavaScript 需要管理比任何时候都要多的 state (状态)。 这些 state 可能包括服务器响应、缓存数据、本地生成尚未持久化到服务器的数据,也包括 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。

管理不断变化的 state 非常困难。如果一个 model 的变化会引起另一个 model 变化,那么当 view 变化时,就可能引起对应 model 以及另一个 model 的变化,依次地,可能会引起另一个 view 的变化。直至你搞不清楚到底发生了什么。state 在什么时候,由于什么原因,如何变化已然不受控制。 当系统变得错综复杂的时候,想重现问题或者添加新功能就会变得举步维艰。

store,action,reducer是如何协调工作的?

假设我们有一个事情清单要维护:

const state = {
  todos: [
    {
      text: 'eat food',
      completed: true,
    },
    {
      text: 'Exercise',
      completed: false
    },
  ]
};

我们如何去维护这些数据? 比如增加一个要做的事情.

reducer就是做这个工作的,它完成了数据的初始化,并且决定了对这个数据有哪些行为.并且它还是一个纯函数(即不对参数进行修改的函数),要保证只要输入的参数一样,那么结果一定相同.

const defaultState = {
  todos: [],
}
function todoReducer(state=defaultState, action) {
  switch (action.type) {
    case 'insert':
      const todo = { text: action.text, completed: false };
      return { ...state, todos: [...state.todos, todo] };
    default:
      return { ...state };
  }
}

我们只能够通过这个todoReducer来操作数据,以上我们需要传入行为action,定义操作的类型以及数据.显然这样通过字符串的形式来判断行为,是不行的.所以我们可以预先定义action,然后再传入,可以联想到设计模式中的工厂模式.

action.js:

export const actionType = {
  insert: 'insert',
}

export const insertTodo = (value)=>({
  type: actionType.insert,
  text: value,
});

acitonType规定了aciton的有那些行为,并且提供了一些action,这里我们演示只有一个增加的action.

然后我们reducer就能够写成:

import { actionType } from './action';

const defaultState = {
  todos: [],
}
function todoReducer(state = defaultState, action) {
  switch (action.type) {
    case actionType.insert:
      const todo = { text: action.text, completed: false };
      return { ...state, todos: [...state.todos, todo] };
    default:
      return { ...state };
  }
}
export default todoReducer;

这样子的话,我们可以通过获取action,给reducer来完成我们对数据的操作.

那么我们如何获取state数据? 如果有一个getter函数就好了,这就是store,并且它还封装了reducer.

store.js

import todoReducer from './todoReducer';

function createStore(reducer) {
  let state = undefined;
  const dispatch = (action) => {
    state = reducer(state, action);
  }
  // 初始化state
  dispatch({});
  const getState = () => state;
  return { getState, dispatch };
}

const store = createStore(todoReducer);
export default store;

这里的store有获取state的方法getState,还有对其操作的函数dispatch.故我们只要通过store就能够对数据进行操作.

示例:

import store from './store';
import { insertTodo } from './action';
import { Component } from 'react';

class Test extends Component {
  constructor(props) {
    super(props);
    console.table(store.getState());
    store.dispatch(insertTodo('run'));
    store.dispatch(insertTodo('write a blog'));
    console.table(store.getState());
  }
  render() { 
    return (null);
  }
}
 
export default Test;


这里我们增加了两个事件,验证一下:
在这里插入图片描述

这就是redux的工作原理,还有一个问题就是store就像是全局变量,可能两个组件的数据变量可能会重复,会出现不方便维护,解决方案是我们为每个组件设置成单独的数据域,就好像:

{
    app:{...},
    todoList: {...},
    ...
}

类似这种效果,这样也不会产生数据的污染.

redux恰好就提供了combineReducers函数来实现这个效果.它就是结合reducer来为索引.

先创建一个总的 reducer:

import { combineReducers } from 'redux';
import todoReducer from './todoReducer';

const rootReducer = combineReducers({
  todoReducer,
});

export default rootReducer;

这里将刚才的todoReducer放进去总reducer.

然后我们的store引入该reducer:

import rootReducer from './rootReducer';
import { createStore } from 'redux';

const store = createStore(rootReducer);
export default store;

createStore,是redux已经帮我们写好了,我们直接用就行.

  constructor(props) {
    super(props);
    console.table(store.getState());
    store.dispatch(insertTodo('run'));
    store.dispatch(insertTodo('write a blog'));
    console.table(store.getState());
  }

我们同样的对其进行打印,看看数据是否有变化.
在这里插入图片描述

我们可以看到,store保存的数据结构已经发生了变化,已经开始分区域保存数据,实际保存数据的其实是reducer,只不过store包含了所有的reducer.

结合 react-redux

React-ReduxRedux的官方React绑定库。它能够使你的React组件从Redux store中读取数据,并且向store分发actions以更新数据.

react-redux提供了两个东西,<Provider/>组件和connect函数.

React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。

UI 组件:

只负责 UI 的呈现,不带有任何业务逻辑,没有状态(即不使用this.state这个变量,所有数据都由参数(this.props)提供,

不使用任何 Redux 的 API.

容器组件:

容器组件的特征恰恰相反,负责管理数据和业务逻辑,不负责 UI 的呈现,带有内部状态,使用 Redux 的 API

Provider

React-Redux 提供<Provider/>组件,能够使你的整个app访问到Redux store中的数据.

源码:

class Provider extends Component {
  getChildContext() {
    return {
      store: this.props.store
    };
  }
  render() {
    return this.props.children;
  }
}

可以看到其作用就是将store绑定在了上下文对象context,然后子组件就可以通过context获取到store.

connect()

connect方法,用于从 UI 组件生成容器组件.

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

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

    render () {
      const { store } = this.context
      let stateProps = mapStateToProps(store.getState())
      // {...stateProps} 意思是把这个对象里面的属性全部通过 `props` 方式传递进去
      return <WrappedComponent {...stateProps} />
    }
  }

  return Connect
}

它从context获取了store,并且将store里的数据,通过props注入到了组件,所以组件可以通过props里读取store里的数据,而mapStateToProps,顾名思义就是将store里的state映射到组件的props.

还能继续深入对dispatch进行映射,同样映射到props里,并且数据dispatch之后要重新渲染组件.

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

    constructor () {
      super()
      this.state = {
        allProps: {}
      }
    }

    componentWillMount () {
      const { store } = this.context
      this._updateProps()
      // 订阅,只要dispatch就会调用传入的函数
      store.subscribe(() => this._updateProps())
    }

    _updateProps () {
      const { store } = this.context
      let stateProps = mapStateToProps
        ? mapStateToProps(store.getState(), this.props)
        : {} // 防止 mapStateToProps 没有传入
      let dispatchProps = mapDispatchToProps
        ? mapDispatchToProps(store.dispatch, this.props)
        : {} // 防止 mapDispatchToProps 没有传入
      this.setState({
        allProps: {
          ...stateProps,
          ...dispatchProps,
          ...this.props
        }
      })
    }

    render () {
      return <WrappedComponent {...this.state.allProps} />
    }
  }
  return Connect
}

这样就也就完成了statedispatch映射到props的过程.

实际上react-redux已经封装了该函数,所以我们直接用就行了.

示例: 将 reduxreact结合起来,图像化.

import React from 'react';
import TodoList from './components/react-redux/TodoList';
import { connect } from 'react-redux';
import { insertTodo } from './store/action'

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { value: '' };
  }
  handleChange = (e) => {
    const value = e.target.value;
    this.setState({
      value,
    });
  }
  handleClick = () => {
    this.props.insertTodo(this.state.value);
    this.setState({
      value:'',
    });
  }
  render() {
    return (
      <div>
        <input value={this.state.value} onChange={this.handleChange} />
        <button onClick={this.handleClick}>add</button>
        <TodoList todos={this.props.todos} />
      </div>
    );
  }
}
const mapToStateToProps = (state) => {
  return {
    todos: state.todoReducer.todos,
  }
}
const mapToDispatchToProps = (dispatch) => {
  return {
    insertTodo: (value) => { dispatch(insertTodo(value)) },
  }
}
export default connect(mapToStateToProps, mapToDispatchToProps)(App);

这里我们返回的是一个由connect生成的容器组件.并且完成了映射,将state.todoReducer.todos映射到了props.todos上,dispatch也是如此.所以在组件里,我们直接用props调用就行了,TodoList就是简单的react组件.

附上TodoList.js:

import React, { Component } from 'react';
import Todo from './Todo';

class TodoList extends Component {
  render() {
    const { todos } = this.props;
    return (
      <ul>
        {todos.map(todo => {
          return <Todo todo={todo} />;
        })}
      </ul>
    );
  }
}

export default TodoList;

以及Todo.js:

import React, { Component } from 'react';

class Todo extends Component {
  render() {
    const { todo } = this.props;
    return (
      <div>
        {todo.text}
        {todo.completed ? <input type='checkbox' checked />
          : <input type='checkbox' />}
      </div>
    );
  }
}

export default Todo;

记住一定要connect生成的组件一定要在<Provider/>下才能有效,因为connect要通过context获取store,而store放在context的过程是有<Provider/>,前面已经讲过了.

import { Provider } from 'react-redux';
import store from './store/store';
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>, document.getElementById('root'));

最后的效果:
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值