前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Redux(一):基本概念

Redux(一):基本概念

原创
作者头像
Ashen
修改2020-06-01 14:29:35
1.3K0
修改2020-06-01 14:29:35
举报
文章被收录于专栏:Ashenの前端技术Ashenの前端技术

背景

React是一个单向数据流的view层框架,单向数据流、组件化、生命周期是其特点。在React组件关系中,组件状态由自己管理,父子组件通过props传递;兄弟组件那么就需要一个共同的父组件作中转;如果涉及层级比较深的话一层一层传递会非常麻烦。所以大量状态共享是React单独难以解决的问题。

随着单页面应用的日益复杂,JavaScript需要维护更多的状态,这些状态除了包含服务端返回的数据还有:缓冲数据、未同步到服务端的持久化数据、UI状态等。如果能将这些状态从单个组件剥离出来统一管理,将会更好的维护、拓展Web应用。

Redux就是JavaScript应用这样一个可预测化的状态管理容器。Redux本身和React其实并没有任何关系,只是二者共性的函数式编程配合起来会比较方便,当然实际React项目中还要用到react-redux做桥接。

三大原则

一、单一数据源

应用的state保存在一个JavaScript对象树中,并且这个对象树只能存在于唯一的一个store中。

import {createStore } from 'redux';
const store = createStore(reducer);

二、state是只读的

唯一改变state的方法就是触发action,action是一个描述state如何改变的普通对象,必须包含type属性。

store.dispatch({
    type: 'COMPLETE_TODO',
    index: 1
});
store.dispatch({
    type: 'SET_VISIBILITY_FILTER',
    filter: 'SHOW_COMPLETED'
});

三、使用纯函数来执行修改

dispatch一个action以后,如何根据这个普通对象来修改state树,那么就需要编写对应的函数,这个函数称之为reducers。reducers必须是纯函数,所谓纯函数可以简单理解为:只要输入相同那么输出就相同,同样的输入只会输出同一个结果。

随着应用规模的增长,所要维护的state树会变的很大,这样就需要把reducers拆分成多个reducer,每个reducer来维护状态树的一部分。

function visibilityFilter(state = 'SHOW_ALL', action) {
  switch (action.type) {
    case 'SET_VISIBILITY_FILTER':
      return action.filter
    default:
      return state
  }}
function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case 'COMPLETE_TODO':
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: true
          })
        }
        return todo
      })
    default:
      return state
  }
}

快速上手

0、开始之前

这里以react来演示,当然还是得记住那句话,”redux和react没有任何关系”。

现在设计这么一个状态树:

{
    visibilityFilter:"SHOW_ALL",
    todos:[{
        text:"consider use redux",
        completed:true
    },{
        text:"keep all state in a single tree",
        completed:true
    }]
}

这个对象包含2个属性:visibilityFilter、todos。visibilityFilter表示过滤类型,值是一个字符串;todos表示待办事项,值是一个数组。

可以为todos新增或删除项目,也可以改变某个项目的完成情况——completed。

1、安装redux、react、react-dom

npm install redux react react-dom --save

示例对应版本: – reudx:4.0.1 – react:16.6.3 – react-dom:16.6.3

2、编写一个reducer

创建一个reducers.js,编写以下代码:

const initState = {
    visibilityFilter:"SHOW_ALL",
    todos:[]
};
function reducers(state=initState,action){
    return state;
}
 
export {reducers}

reducer接收2个参数:state、action。现在函数内部什么都没有做,仅仅是返回state,后续再增加相关逻辑判断。

3、创建一个store

在redux中应该只有一个store,单一数据源,这一点很重要。redux向外暴露了一个createStore方法用来创建store。

所以,创建一个store.js,编写以下代码:

import {createStore} from "redux";
import {reducers} from "./reducers";
 
const store = createStore(reducers);
 
export default store;

4、创建一个react组件

现在实现这么个功能:一个input框用来输入待办事项,点击提交按钮将数据加到todos中,初始状态completed为false,点击完成将对应的这一条改为true。同时增加一个下拉框select,用来筛选todos。

创建一个app.js,编写以下代码:

import React from "react";
import ReactDOM from "react-dom";
 
class App extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            filterList:[
                {label:"全部",value:"SHOW_ALL"},
                {label:"已完成",value:"SHOW_COMPLETED"},
                {label:"未完成",value:"SHOW_UNCOMPLETED"},
            ],
            visibilityFilter:"SHOW_ALL",
            todoValue:"",
            todos:[]
        }
    }
    render(){
        const {filterList,visibilityFilter,todoValue,todos} = this.state;
        return(
            <div>
                <div>
                    <label>过滤类型:</label>
                    <select value={visibilityFilter} onChange={this.filterChange}>
                        {
                            filterList.map(item=>(
                                <option key={item.value} value={item.value}>{item.label}</option>
                            ))
                        }
                    </select>
                </div>
                <div>
                    <input placeholder={"请输入待办事项"} value={todoValue} onChange={this.todoChange} />
                    <button onClick={this.submitTodo} type={"button"}>提交</button>
                </div>
                <div>
                    <table border={"border"}>
                        <thead>
                            <tr>
                                <th>事项</th>
                                <th>是否完成</th>
                                <th>操作</th>
                            </tr>
                        </thead>
                        <tbody>
                            {
                                this.getFilterTodos(todos,visibilityFilter).map((item,index)=>(
                                    <tr key={index}>
                                        <td>{item.text}</td>
                                        <td>{item.completed.toString()}</td>
                                        <td>
                                            {
                                                !item.completed &&
                                                <button onClick={()=>{this.completeTodo(index)}} type={"button"}>完成</button>
                                            }
                                        </td>
                                    </tr>
                                ))
                            }
                        </tbody>
                    </table>
                </div>
            </div>
         )
    }
    //监听过滤条件
    filterChange=(e)=>{
        const visibilityFilter= e.target.value;
        this.setState({
           visibilityFilter
        });
    };
    //监听input
    todoChange=(e)=>{
        const todoValue = e.target.value;
        this.setState({
            todoValue
        });
    };
    //提交事项
    submitTodo=()=>{
        const {todoValue,todos} = this.state;
        this.setState({
            todos:[].concat(todos).concat({
                text:todoValue,
                completed:false
            }),
            todoValue:""
        });
    };
    //完成事项
    completeTodo=(i)=>{
        const {todos} = this.state;
        const newTodos = todos.map((item,index)=>{
            if(index !== i){
                return item;
            }
            return Object.assign({},item,{completed:true})
        });
        this.setState({todos:newTodos});
    };
    //获取筛选后的todos
    getFilterTodos=(todos,visibilityFilter)=>{
        switch (visibilityFilter) {
            case "SHOW_ALL":
                return todos;
            case "SHOW_COMPLETED":
                return todos.filter(item=>item.completed);
            case "SHOW_UNCOMPLETED":
                return todos.filter(item=>!item.completed);
            default:
                return todos;
        }
    };
}
 
ReactDOM.render(<App/>,document.querySelector("#root"));

这个组件是纯state维护状态的版本,现在将todos和visibilityFilter拆分到store中:

import React from "react";
import ReactDOM from "react-dom";
import store from "./store/store";
 
class App extends React.Component{
  constructor(props){
    super(props);
    this.state = {
      filterList:[
        {label:"全部",value:"SHOW_ALL"},
        {label:"已完成",value:"SHOW_COMPLETED"},
        {label:"未完成",value:"SHOW_UNCOMPLETED"},
      ],
      visibilityFilter:store.getState().visibilityFilter,
      todoValue:"",
      todos:store.getState().todos
    }
  }
  render(){
    const {filterList,visibilityFilter,todoValue,todos} = this.state;
    return(
      <div>
        <div>
          <label>过滤类型:</label>
          <select value={visibilityFilter} onChange={this.filterChange}>
            {
              filterList.map(item=>(
                <option key={item.value} value={item.value}>{item.label}</option>
              ))
            }
          </select>
        </div>
        <div>
          <input placeholder={"请输入待办事项"} value={todoValue} onChange={this.todoChange} />
          <button onClick={this.submitTodo} type={"button"}>提交</button>
        </div>
        <div>
          <table border={"border"}>
            <thead>
            <tr>
              <th>事项</th>
              <th>是否完成</th>
              <th>操作</th>
            </tr>
            </thead>
            <tbody>
            {
              this.getFilterTodos(todos,visibilityFilter).map((item,index)=>(
                <tr key={index}>
                  <td>{item.text}</td>
                  <td>{item.completed.toString()}</td>
                  <td>
                    {
                      !item.completed &&
                      <button onClick={()=>{this.completeTodo(index)}} type={"button"}>完成</button>
                    }
                  </td>
                </tr>
              ))
            }
            </tbody>
          </table>
        </div>
      </div>
    )
  }
  componentDidMount(){
    store.subscribe(()=>{
      this.setState({
        visibilityFilter:store.getState().visibilityFilter,
        todos:store.getState().todos
      });
    });
  }
  //监听过滤条件
  filterChange=(e)=>{
    store.dispatch({
      type:"VISIBILITY_FILTER_SET",
      value:e.target.value
    });
  };
  //监听input
  todoChange=(e)=>{
    const todoValue = e.target.value;
    this.setState({
      todoValue
    });
  };
  //提交事项
  submitTodo=()=>{
    const {todoValue} = this.state;
    store.dispatch({
      type:"TODOS_ADD",
      todo:{
        text:todoValue,
        completed:false
      }
    });
    this.setState({
      todoValue:""
    });
  };
  //完成事项
  completeTodo=(index)=>{
    store.dispatch({
      type:"TODOS_COMPLETED",
      todo:{
        index,
        completed:true
      }
    });
  };
  //获取筛选后的todos
  getFilterTodos=(todos,visibilityFilter)=>{
    switch (visibilityFilter) {
      case "SHOW_ALL":
        return todos;
      case "SHOW_COMPLETED":
        return todos.filter(item=>item.completed);
      case "SHOW_UNCOMPLETED":
        return todos.filter(item=>!item.completed);
      default:
        return todos;
    }
  };
}
 
ReactDOM.render(<App/>,document.querySelector("#root"));

store的dispatch()方法用来派发一个action,action是一个普通对象,必须包含type属性,这个属性用来标识执行对应的reducer。

store.subscribe()方法用来监听store里state的变化,所以我们在subscribe的回调里重新获取store的state,以此来更新我们组件的state。

这里共三种action,分别为:VISIBILITY_FILTER_SET(设置过滤类型)、TODOS_ADD(新增事项)、TODOS_COMPLETED(完成事项)。所以我们的reducer需要对这三种情况做判断。

5、修改reducer

const initState = {
  visibilityFilter:"SHOW_ALL",
  todos:[]
};
function reducers(state=initState,action){
  switch (action.type){
    case "VISIBILITY_FILTER_SET":
      return Object.assign({},state,{visibilityFilter:action.value});
    case "TODOS_ADD":
      return Object.assign({},state,{todos:state.todos.concat(action.todo)});
    case "TODOS_COMPLETED":
      return Object.assign(
        {},
        state,
        {
          todos:state.todos.map((item,index)=>{
            if(index !== action.todo.index){
              return item;
            }
            return Object.assign({},item,{completed:action.todo.completed})
          })
        }
      );
    default:
      return state;
  }
}
export {reducers}

这里使用switch语句,根据不同的action.type执行不同的操作,返回的都是修改后的state树。

例子中,无论是对象还是数组,并没有直接去修改属性会增加元素,返回的都是一个新的对象或数组,这一点很重要,因为在js中对象是按地址引用的,直接修改属性或push一个元素,引用地址并没有发生变化,这会导致出现一些难以控制的情况。所以,在redux中不应该使用如:push、pop、slice等方法。对于数组可以用concat、拓展运算符、map等;对于对象可以用Object.assign()、拓展运算符等。

总结:

可以看到Redux使用的是派发/监听的设计模式,每次派发action,reducer运算结束后会执行在subscribe注册的回调函数。试想一个问题,如果我的组件之前注册了一个subscribe,然后组件销毁了,当组件又重新渲染的时候便会再次注册subscribe,那么这时派发一个action后,会怎么样?

事实证明,会执行2次,但由于第一次的组件销毁了,所以在一个已经销毁的组件上执行setState()方法必然是不合理的,此时react会抛出一个警告:

Can’t perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.

意思就是:不能在一个已经卸载的组件上执行更新state的操作,这会导致内存泄漏, 应该在componentWillUnmount生命周期中取消所有订阅和异步任务。

redux本身并没有取消订阅的方法,所以实际react+redux项目中,还要用到桥接二者的工具——react-redux。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 三大原则
    • 一、单一数据源
      • 二、state是只读的
        • 三、使用纯函数来执行修改
        • 快速上手
          • 0、开始之前
            • 1、安装redux、react、react-dom
              • 2、编写一个reducer
                • 3、创建一个store
                  • 4、创建一个react组件
                    • 5、修改reducer
                    • 总结:
                    相关产品与服务
                    容器服务
                    腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档