Redux入门实战——todo

Stella981 等级 263 0 0
标签: reduxreactconst

1.前言

在之前的博客中,我写了一篇关于todo-list实现的博客,一步一步详细的记录了如何使用基础的React知识实现一个React单页面应用,通过该篇文章,能够对React入门开发有一个直观的认识和粗浅的理解。

近期,个人学习了一下Redux,又将该项目使用 React+Redux的方式进行了实现。本片内容记录以下实践的过程。通过本实例,可以学习到:

  • Redux的核心思想;
  • Redux的三大概念;
  • React+Redux的开发方法和流程;

下面将从以下几个方面展开讲解和记录。

2.项目演示

Redux入门实战——todo

3.Redux基础知识

3.1 认识

3.1.1 动机

随着 JavaScript 单页面应用开发日趋复杂,JavaScript 需要管理比任何时候都要多的 state (状态),管理不断变化的 state 非常困难,state 在什么时候,由于什么原因,如何变化已然不受控制。当系统变得错综复杂的时候,想重现问题或者添加新功能就会变得举步维艰。

因此,需要一种更可控的方式来管理系统的state,让系统的state变得可预测,redux就是用来管理系统state的工具。

3.1.2 三大原则

  • 单一数据源

    整个应用的状态都保存在一个对象中,一个应用只有一个唯一的state,保存在store中,通过store统一管理。

  • 状态是只读的

    唯一改变 state 的方法就是触发 actionaction 是一个用于描述已发生事件的普通对象。

    redux不会直接修改state,而是在状态发生更改时,返回一个全新的状态,旧的状态并没有进行更改,得以保留。可以使用 redux-devtools-extension 工具进行可视化查看。

  • 状态修改由纯函数完成

    Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。

3.2 基础

3.2.1 Store

Redux的核心是 Store ,StorecreateStore方法创建,

createStore(reducer, [initState])//reducer表示一个根reducer,initState是一个初始化状态

store提供方法来操作state

3.2.2 Action

action 是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源。通过 store.dispatch() 将 action 传到 store。如果有数据需要添加,在action中一并传过来。

action需要action创建函数进行创建,如下是一个action创建函数:

/*
 * action 类型
 */

export const ADD_TODO = 'ADD_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO'
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'

/*
 * 其它的常量
 */

export const VisibilityFilters = {
  SHOW_ALL: 'SHOW_ALL',
  SHOW_COMPLETED: 'SHOW_COMPLETED',
  SHOW_ACTIVE: 'SHOW_ACTIVE'
}

/*
 * action 创建函数
 */

export function addTodo(text) {
  return { type: ADD_TODO, text }
}

export function toggleTodo(index) {
  return { type: TOGGLE_TODO, index }
}

export function setVisibilityFilter(filter) {
  return { type: SET_VISIBILITY_FILTER, filter }
}

返回一个对象,改对象由reducer获取,根据 action 类型进行相应操作。

3.2.3 Reducer

store通过 store.dispatch(某action(参数)) 来给reducer安排任务。

简单理解,一个reducer就是一个函数,这个函数接受两个参数 当前stateaction,然后根据 action 来对当前 state 进行操作,如果有需要更改的地方,就返回一个 新的 state ,而不会对旧的 state进行操作,任何一个阶段的 state 都可以进行查看和监测,这让 state 的管理变得可控,可以实时追踪 state的变化。

React中使用Redux时,需要有一个根 Reducer,这个根 Reducer 通过 conbineReducer() 将多个子 Reducer 组合起来。

根reducer:

import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'
//根reducer
// rootReducer 根reducer,把子reducer组合在一起
export default combineReducers({
  todos, //子state
  visibilityFilter //子state
})

子reducer:

//这里的state = []为state的当前值
const todos = (state = [], action) => {
    switch (action.type) {
      case 'ADD_TODO':
        return [
          ...state,     // Object.assign() 新建了一个副本
          {
            id: action.id,
            text: action.text,
            completed: false
          }
        ]
      case 'TOGGLE_TODO':
     //   console.log(state);
        return state.map((value,index) => {
            return (value.id === action.id) ? {...value,completed:!value.completed} : value;
        }) 
      default:
        return state;
    }
  }

export default todos;

3.2.4 数据流

Redux入门实战——todo

3.3 展示组件和容器组件

3.3.1 展示组件和容器组件分离

本部分在笔者尚未深入研究,在此给出redux作者写的深度解析文章链接及网上的译文链接,读者可自行查看。

原文链接:展示组件和容器组件相分离

译文链接:展示组件和容器组件相分离

3.3.2 展示组件和容器组件比较

展示组件

容器组件

作用

描述如何展示骨架、样式

描述如何运行(数据获取、状态更新)

直接使用Redux

数据来源

props

监听Redux state

数据修改

从props调用回调函数

向Redux派发action

调用方式

手动

通常由React Redux生成

大部分的组件都应该是展示型的,但一般需要少数的几个容器组件把它们和 Redux store 连接起来。

React Redux 的使用 connect() 方法来生成容器组件。

import { connect } from 'react-redux'
import { setVisibilityFilter } from '../actions'
import Link from '../components/Link'

//mapStateToProps参数中的state是store的state.
// 在容器组件中,通过mapStateToProps方法,在展示组件和store中间传递数据和执行action
// ownProps表示的是组件自身的属性,即父组件传过来的属性
const mapStateToProps = (state, ownProps) => {
    return {
        active: ownProps.filter === state.setVisibilityFilter
    }
}
// ownProps表示的是组件自身的属性,即父组件传过来的属性
const mapDispatchToProps = (dispatch, ownProps) => {
    return {
        // 这里写方法名,在展示组件中通过这个方法名来执行里面的action派遣函数
        onClick: () => {
            // 执行setVisibilityFilter这个action
            dispatch(setVisibilityFilter(ownProps.filter))
        }
    }
}
//通过connect让Link组件得以连接store,从store中取得active数据和onClick方法的执行体。
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Link)

connect() 中最核心的两个方法是:mapActionToPropsmapDispatchToProps ,通过容器组件,可以在 展示组件和 store之间传递数据和执行 action

4.基于Redux的React项目实战

4.1 目录结构

根据Redux的几大组成部分,在进行开发时,将在之前基础的React开发模式下,增加几个文件夹,形成新的开发目录结构,具体目录结构如下图:

│  App.css
│  App.js
│  App.test.js
│  index.css
│  index.js
│  logo.svg
│  readme.txt
│  serviceWorker.js
│  setupTests.js
├─actions      
├─components       
├─containers
└─reducers

Redux入门实战——todo

如图,在之前的结构下,新增了 actionsreducerscontainers 这三个文件夹。

4.2 配置React-Redux开发环境

4.2.1 步骤

在建好文件目录后就可以开始进行开发了,由于是基于Redux做React开发,所以首先一步当然需要把Redux的开发环境配置一下。

  • 安装 react-redux

    npm install --save react-redux

  • 编写入口文件 index.js

前文讲到,redux使用一个唯一的 store 来对项目进行状态管理,那么首先我们需要创建这个 store ,并将这个 store 作为一个属性,传递给下级子组件。

具体代码如下:

import React from 'react';
import ReactDOM, { render } from 'react-dom';

//redux ----------------------------------------------------
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { rootReducer } from './reducers';
//引入项目根组件App.jsx
import App from './App';

//创建store,将根Reducer传入store中。redux应用只有一个单一的store
const store = createStore(rootReducer);

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

如上代码所示,使用Redux,需要引入的文件有:

  • Provider 组件
  • createStore 方法
  • 根reducer
  • 项目根组件App.jsx

createStorecreateStore 方法可接受两个参数,第一个是项目的根 reducer ,是必选的参数,另一个是可选的参数,可输入项目的初始 state 值。通过该方法创建一个 store 实例,即为项目唯一的 store

Provider组件Provider组件包裹在跟组件App.jsx外层,将项目的 store作为属性传递给 Provider。使用Provider 可以实现所有子组件直接对 store 进行访问。在下文将深入讲一下 Provider 的实现和工作原理。

根reducer:随之项目的不断增大,程序state的越来越复杂,只用一个 reducer 是很难满足实际需求的,redux中采用将 reducer 进行拆分,最终在状态改变之前通过 根 reducer 将 各个拆分的子 reducer 进行合并方式来进行处理。

App.jsx:项目的跟组件,将一级子组件写在App.jsx中。

4.2.2 Provider

provider 包裹在根组件外层,使所有的子组件都可以拿到state。它接受store作为props,然后通过context往下传,这样react中任何组件都可以通过context获取store。

Provider 原理:

原理是React组件的context属性

组件源码如下:

原理是React组件的context属性

export default class Provider extends Component {
  getChildContext() {
      //返回一个对象,这个对象就是context
    return { store: this.store }
  }

  constructor(props, context) {
    super(props, context)
    this.store = props.store
  }
  render() {
    return Children.only(this.props.children)
  }
}

Provider.propTypes = {
  store: storeShape.isRequired,
  children: PropTypes.element.isRequired
}

Provider.childContextTypes = {
  store: storeShape.isRequired
}

4.3 src目录文件列表

文件夹

文件

src

index.js

src/actions

index.js

src/components(展示组件)

App.jsx

TodoList.jsx

Footer.jsx

Todo.jsx

Link.jsx

src/containers(容器组件)

AddTodo.js

FilterLink.js

VisibleTodoList.js

src/reducers

index.js

todo.jsx

visibilityFilter.js

4.4 项目代码

注意:

  • 代码说明大部分写在项目代码中,读者在查看时,建议对代码也要进行仔细阅读。
  • 本项目功能较简单,因此代码直接按照文件目录给出,而不按照功能模块陈列。

4.4.1 入口文件 index.js

import React from 'react';
import ReactDOM, { render } from 'react-dom';
import './index.css';
import App from './components/App';

//redux
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import rootReducer from './reducers';

//创建store,createStore()第一个参数是项目的根reducer,第二个参数是可选的,用于设置state的初始状态
const store = createStore(rootReducer);

render(
  // Provider组件包裹在跟组件的外层,使所有的子组件都可以拿到state.
  // 它接受store作为props,然后通过context往下传,这样react中任何组件
  // 都可以通过context获取store.
  <Provider store = {store}>
    {/* App 根组件 */}
    <App />
  </Provider>,
  document.getElementById('root')
)

4.4.2 actions文件

  • index.js

    let nextTodoId = 0;

    // 定义action 常量 对于小型项目,可以将action常量和action创建函数写在一起,对于复杂的项目,可将action常量和其他的常量抽取出来,放到单独的某个常量文件夹中 const ADD_TODO = 'ADD_TODO'; const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'; const TOGGLE_TODO = 'TOGGLE_TODO';

    //这里是几个action创建函数,函数里面的对象才是action,返回一个action // text是跟随action传递的数据 // 调用 dispatch(addTodo(text)),即代表派遣action,交给reducer处理 //action生成函数 // 大部分情况下,他简单的从参数中收集信息,组装成一个action对象并返回, // 但对于较为复杂的行为,他往往会容纳较多的业务逻辑与副作用,包括与后端的交互等等。 export const addTodo = (text) => { return { type: ADD_TODO, id: nextTodoId ++, text } } export const setVisibilityFilter = (filter) => { return { type: SET_VISIBILITY_FILTER, filter } } export const toggleTodo = (id) => { return { type: TOGGLE_TODO, id } } //三个常量 export const VisibilityFilters = { SHOW_ALL: 'SHOW_ALL', SHOW_COMPLETED: 'SHOW_COMPLETED', SHOW_ACTIVE: 'SHOW_ACTIVE' }

4.4.3 components文件(展示组件)

  • App.jsx

    import React from 'react' import Footer from './Footer' import AddTodo from '../containers/AddTodo' import VisibleTodoList from '../containers/VisibleTodoList' //应用的根组件 const App = () => { return (

    {/* 容器组件 /} {/ 容器组件 /} {/ 展示组件 */}
    )
    } export default App

  • Footer.jsx

    import React from 'react' import FilterLink from '../containers/FilterLink' import { VisibilityFilters } from '../actions' //无状态组件,这种写法初学者可能难以理解,可以先补习下ES6,等价于 //function Footer(){ // return (

    XXX
    ) //} const Footer = () => (

    Show: All Active Completed
    ) export default Footer
  • Link.jsx

    import React from 'react' import PropTypes from 'prop-types' //prop-types是一个组件属性校验包,导入这个包可以数据进行格式等方面的校验 const Link = (props) => { return ( <button onClick={props.onClick} disabled={props.active} style={{marginLeft:'4px'}}> {props.children} ) }

    Link.propTypes = { active: PropTypes.bool.isRequired, children: PropTypes.node.isRequired, onClick: PropTypes.func.isRequired }

    export default Link

  • TodoList.jsx

    import React, { createFactory } from 'react' import PropTypes from 'prop-types' import Todo from './Todo'

    const TodoList = (props) => { return (

      { props.todos.map((value,index) => { return <Todo key = {index} {...value} onClick = {() => props.toggleTodo(value.id)} /> }) }
    ) }

    TodoList.propTypes = { todos: PropTypes.arrayOf( PropTypes.shape({ id: PropTypes.number.isRequired, completed: PropTypes.bool.isRequired, text: PropTypes.string.isRequired }).isRequired ).isRequired, toggleTodo: PropTypes.func.isRequired }

    export default TodoList

  • Todo.jsx

    import React from 'react' import PropTypes from 'prop-types'

    const Todo = ({ onClick, completed, text }) => (

  • {text}
  • )

    Todo.propTypes = { onClick: PropTypes.func.isRequired, completed: PropTypes.bool.isRequired, text: PropTypes.string.isRequired }

    export default Todo

4.4.4 containers文件(容器组件)

注意:本部分涉及 connect() 方法,代码注释中有重要知识点,建议仔细查看。对于connect()本文不做深入探讨,后续会单独成文分析。

  • FilterLink.js

    import { connect } from 'react-redux' import { setVisibilityFilter } from '../actions' import Link from '../components/Link' import { createFactory } from 'react'

    //mapStateToProps参数中的state是store的state. // 在容器组件中,通过mapStateToProps方法,在展示组件和store中间传递数据和执行action // ownProps表示的是组件自身的属性,即父组件传过来的属性 const mapStateToProps = (state, ownProps) => { return { active: ownProps.filter === state.setVisibilityFilter } }

    // ownProps表示的是组件自身的属性,即父组件传过来的属性 const mapDispatchToProps = (dispatch, ownProps) => { return { // 这里写方法名,在展示组件中通过这个方法名来执行里面的action派遣函数 onClick: () => { // 执行setVisibilityFilter这个action dispatch(setVisibilityFilter(ownProps.filter)) } } }

    //通过connect让Link组件得以连接store,从store中取得active数据和onClick方法的执行体。 export default connect( mapStateToProps, mapDispatchToProps )(Link)

    // //将Link组件的内容放到本页面来结合起来理解,以下代码不是本组件的功能代码 // const Link = ({ active, children, onClick }) => ( // <button // onClick={onClick} // disabled={active} // style={{ // marginLeft: '4px', // }} // > // {children} // // )

    // Link.propTypes = { // active: PropTypes.bool.isRequired, // children: PropTypes.node.isRequired, // onClick: PropTypes.func.isRequired // }

建议将容器组件和它对应的展示组件紧密结合起来理解。

  • AddTodo.js

    import React from 'react' import { connect } from 'react-redux' import { addTodo } from '../actions'

    const AddTodo = ({ dispatch }) => { let input

    return (

    <form onSubmit={e => { e.preventDefault() if (!input.value.trim()) { return } dispatch(addTodo(input.value)) input.value = '' }} > <input ref={node => input = node} />
    ) }

    export default connect()(AddTodo);

  • VisibleTodoList.js

    import { connect } from 'react-redux' import { toggleTodo } from '../actions' import TodoList from '../components/TodoList'

    //获取符合条件的todo, // todos state中的todo数据 // filter state中的过滤条件 const getVisibleTodos = (todos, filter) => { switch (filter) { case 'SHOW_COMPLETED': return todos.filter(t => t.completed) case 'SHOW_ACTIVE': return todos.filter(t => !t.completed) case 'SHOW_ALL': default: return todos } } const mapStateToProps = (state) => { return { todos: getVisibleTodos(state.todos, state.visibilityFilter) } } const mapDispatchToProps = (dispatch) => { return { toggleTodo: (id) => { dispatch(toggleTodo(id)) } } }

    export default connect( mapStateToProps, mapDispatchToProps )(TodoList)

4.4.5 reducer文件夹

  • 根reducer/index.js

    import { combineReducers } from 'redux' import todos from './todos' import visibilityFilter from './visibilityFilter' // rootReducer 根reducer,把子reducer组合在一起 export default combineReducers({ todos, //子state visibilityFilter //子state })

  • todo.js

    //这里的state = []为state的当前值 const todos = (state = [], action) => { switch (action.type) { case 'ADD_TODO': return [ ...state, // Object.assign() 新建了一个副本 { id: action.id, text: action.text, completed: false } ] case 'TOGGLE_TODO': // console.log(state); return state.map((value,index) => { return (value.id === action.id) ? {...value,completed:!value.completed} : value; }) default: return state; } }

    export default todos;

  • visibilityFilter.js

    const visibilityFilter = (state = 'SHOW_ALL', action) => { switch (action.type) { case 'SET_VISIBILITY_FILTER': return action.filter default: return state } }

    export default visibilityFilter

5.总结

本文,菜鸡本鸡通过一个todo-list实例相对系统的介绍了redux的一些基础概念,基本用法和如何如react进行结合,实现react的功能开发,主要内容包括redux基础,redux于react结合,实例完成步骤,完整代码,项目演示等,比较适合刚接触redux的菜鸟阅读和学习,希望能帮助到有需要的同学。

6 参考资料

本文同步分享在 博客“CherishTheYouth”(CNBlog)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

收藏
评论区

相关推荐

《彻底掌握redux》之开发一个任务管理平台(上)
前言 redux是上手react开发的必经之路,也是目前react项目中使用的最流行状态管理库。虽然我们不使用redux也可以通过react的state和父子props进行基本的数据通信和项目开发,但是对于一个大型项目而言,往往考虑的更多的是代码结构和组件之间的通信,我们需要一种很优雅且有利于扩展的方式去开发我们的复杂系统,所以这种情况下使用redux是最佳
做了N+1个企业项目之后, 我总结了这些React必备插件
为了提高大家开发 React 项目的效率, 笔者结合自己的实际工作经验, 汇总如下React项目常用插件. 1. 状态管理 Redux JavaScript 状
做了N+1个企业项目之后, 我总结了这些React必备插件
为了提高大家开发 React 项目的效率, 笔者结合自己的实际工作经验, 汇总如下React项目常用插件. 1. 状态管理 Redux JavaScript 状
MobX 上手指南
之前用 Redux 比较多,一直听说 Mobx 能让你体验到在 React 里面写 Vue 的感觉,今天打算尝试下 Mobx 是不是真的有写 Vue 的感觉。 题外话 在介绍 MobX 的用法之前,先说点题外话,我们可以看一下 MobX 的中文简介。在 MobX 的中文网站上写着: MobX 是一个经过战火洗礼的库,它通过透明的函数响应式编程
ng
初衷 -- 我们一直想做一款企业级中后台框架,最初版本是 vue2 的,虽然vue上手非常快,但是我们定位是企业级中后台框架,vue 在做一些复杂项目的时候,感觉架构上确实有些捉襟见肘,遇到的问题也越来越多。后来改用 React ,当时使用了 Typescript 以及 Redux ,第三方组件支持并不友好,使用到最后也逐渐感觉到了 Rxjs 的真香警告,
Fish Redux中的Dispatch是怎么实现的?
零.前言 ---- 我们在使用fish-redux构建应用的时候,界面代码(view)和事件的处理逻辑(reducer,effect)是完全解耦的,界面需要处理事件的时候将action分发给对应的事件处理逻辑去进行处理,而这个分发的过程就是下面要讲的dispatch, 通过本篇的内容,你可以更深刻的理解一个action是如何一步步去进行分发的。 一.从e
React 系列教程2:编写兰顿蚂蚁演示程序
简介 -- 最早接触兰顿蚂蚁是在做参数化的时候,那时候只感觉好奇,以为是很复杂的东西。因无意中看到[生命游戏](https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgithub.com%2Fthepeted%2Fgame-of-life-redux)的 React 实现,所以希望通过兰顿蚂蚁的
React实战之React+Redux实现一个天气预报小项目
![](https://oscimg.oschina.net/oscnet/355a9b3e0af9e3410d9cf918fdfe69b2379.jpg)**引言** ------------------------------------------------------------------------------------ 经过一段时间的Re
React技术栈实现XX点评电商App
> 项目地址:[https://github.com/Nealyang/React-Fullstack-Dianping-Demo](https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgithub.com%2FNealyang%2FReact-Fullstack-Dianping-Demo)
React知识杂烩(持续更新)
每隔半年不看官方文档,你就会不认识React了😁 * React组件生命周期 * 受控组件与非受控组件 * 多个输入的解决方法 * Props.children可以传递任何数据包括函数 * 布尔值、Null 和 Undefined 被忽略 * 使用 PropTypes 进行类型检查(直接参考官方文档) * react-redux
Redux入门到使用。
#### ![](https://oscimg.oschina.net/oscnet/c7b0cf8db4ed63eccfdfe3b7d711ec72da6.gif)简介 Redux是针对JavaScript应用的可预测状态容器。 > 如果熟悉设计模式之观察者模式理解起来就简单了。就是将你在其他组件中需要用到的数据放到一个容器中,那么组件就可以从其中取放
Redux入门实战——todo
1.前言 ==== 在之前的博客中,我写了一篇关于todo-list实现的博客,一步一步详细的记录了如何使用基础的React知识实现一个React单页面应用,通过该篇文章,能够对React入门开发有一个直观的认识和粗浅的理解。 近期,个人学习了一下Redux,又将该项目使用 React+Redux的方式进行了实现。本片内容记录以下实践的过程。通过本实例,
Redux异步解决方案之Redux
前段时间,我们写了一篇[Redux源码分析的文章](https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fjuejin.im%2Fpost%2F6845166891682512909),也[分析了跟`React`连接的库`React-Redux`的源码实现](https://www.oschina
Vue 全家桶
vue全家桶。 使用过vue的程序员一般这样评价它,“vue.js兼具angular.js和react.js的优点”。Vue.js 是一个JavaScript MVVM(Model-View-ViewModel)库,用于渐近式构建用户界面。它以数据驱动和组件化思想构建,采用自底向上增量开发的设计思想。相比Angular.js,Vue.js API更加简洁;
taro 知识点
* taro 的包: 包名 说明 @tarojs/redux Redux for Taro @tarojs/redux-h5 Forked react-redux for taro @tarojs/plugin-csso Taro压缩CSS文件 ### 内置环境变量 `process.env.TARO_ENV`用于判断当前编译类型,目