专栏首页樯橹代码redux原来如此简单

redux原来如此简单

Redux 是 JavaScript 状态容器, 提供可预测化的状态管理。

那什么是可以预测化,我的理解就是根据一个固定的输入,必然会得到一个固定的结果。

redux是专门为react开发的,但并不是只能用于react,可以用于任何界面库。

动机

随着单页面应用的普及,web app内部需要管理的状态越来越多,这些状态可能来自服务器端,用户输入的数据,用户交互数据,当前UI状态,本地的缓存数据等等。如何能够有条理的管理这些数据,成为前端开发中一个难题。

核心概念

三大原则

单一数据源

使用redux的程序,所有的state都存储在一个单一的数据源store内部,类似一个巨大的对象树。

state是只读的

state是只读的,能改变state的唯一方式是通过触发action来修改

使用纯函数执行修改

为了描述 action 如何改变 state tree , 你需要编写 reducers。

reducers是一些纯函数,接口当前state和action。只需要根据action,返回对应的state。而且必须要有返回。

一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数

基础

action

顾名思义,action就是动作,也就是通过动作来修改state的值。也是修改store的唯一途径。

action本质上就是一个普通js对象,我们约定这个对象必须有一个字段type,来表示我们的动作名称。一般我们会使用一个常量来表示type对应的值。

此外,我们还会把希望state变成什么样子的对应的值通过action传进来,那么这里action可能会类似这样子的

{
    type: 'TOGGLE_TODO',
    index: 5
}

Reducer

Action 只是描述了有事情发生了这件事实,但并没有说明要做哪些改变,这正是reducer需要做的事情。

Reducer作为纯函数,内部不建议使用任何有副作用的操作,比如操作外部的变量,任何导致相同输入但输出却不一致的操作。

如果我们的reducer比较多,比较复杂,我们不能把所有的逻辑都放到一个reducer里面去处理,这个时候我们就需要拆分reducer。

幸好,redux提供了一个api就是combineReducers Api。

store

store是redux应用的唯一数据源,我们调用createStore Api创建store。

脱离react的redux案例

store,reducer基础使用

第一步搭建开发环境,这里不介绍了,参考上一篇文章 手把手教会使用react开发日历组件,搭建环境部分

搭建好环境切换到目录下面

npm install redux --save

把index.tsx修改为之下代码。

import { createStore, combineReducers, applyMiddleware } from 'redux'

var simpleReducer = function(state = {}, action) {
  return {
    user: {
      name: 'redux'
    }
  }
}

var store = createStore(simpleReducer)

console.log(store.getState())

我们看到控制台打印出来的一个包含user信息的这么一个对象。

我们使用到了几个api? createStore创建store,store.getState()获取store,也就是唯一数据源的根节点。

上文我们也讲过,action的情况可能会比较多,redux也提供了combineReducers Api。如果我们有多个reducer,我们就可以使用起来了。

那我们创建多个reducer测试一下,代码如下:

import { createStore, combineReducers, applyMiddleware } from 'redux'

function user(state = {name: 'redux'}, action) {
  switch (action.type) {
    case 'CHANGE_NAME':
      return {
        ...state,
        name: action.name
      }
  }

  return state
}

function project(state = {name: 'min-react'}, action) {
  switch (action.type) {
    case 'CHANGE_NAME':
      return {
        ...state,
        name: action.name
      }
  }

  return state
}


var rootReducer = combineReducers({
  user,
  project
})

var store = createStore(rootReducer)

console.log(store.getState())

如我们所预料一样,我们得到拥有两个字段的根store。

结合view使用

第一步我们把html改造成这个样子,新增了一点标签

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style type="text/css">
        * {
            margin: 0;
            padding: 0;
        }
    </style>
</head>
<body>
    <div id="userName"></div>
    <input id="userNameInput"/><button id="userNameButton">更改userName</button>
    <script src="./dist/main.js"></script>
</body>
</html>

第二步,修改index.tsx,如下

import { createStore, combineReducers, applyMiddleware } from 'redux'
import { func } from 'prop-types'

function user(state = {name: 'redux'}, action) {
  switch (action.type) {
    case 'CHANGE_USER_NAME':
      return {
        ...state,
        name: action.name
      }
  }

  return state
}

function project(state = {name: 'min-react'}, action) {
  switch (action.type) {
    case 'CHANGE_PROJECT_NAME':
      return {
        ...state,
        name: action.name
      }
  }

  return state
}


var rootReducer = combineReducers({
  user,
  project
})

var store = createStore(rootReducer)

function render(state = store.getState()) {
  var $userName = document.getElementById('userName')
  $userName.innerHTML = state.user.name
}

render()

console.log(store.getState())

我们看到页面正确的显示了我们user的名称。下一步我们需要做的就是通过用户的操作,改变store的值,进而触发view的更新。

于是我们新增了这块代码:

store.subscribe(function() {
  render()
})

// 绑定用户事件
var $userNameInput = document.getElementById('userNameInput')
var userNameButton = document.getElementById('userNameButton')
userNameButton.onclick = function() {
  var value = $userNameInput.value
  store.dispatch({
    type: 'CHANGE_USER_NAME',
    name: value
  })
}

我们看到保存之后,当我们输入值之后,点击更改,页面的值随着改变。

但是控制台报了一个错误,TS2339: Property 'value' does not exist on type 'HTMLElement'.,这是由于typescript强类型校验没通过导致的。只要加这段代码就好了

var $userNameInput = document.getElementById('userNameInput') as HTMLInputElement

看到了吧,redux就是这么简单。

其他所有上层应用,都是在此基础上开发的,所以开发一个redux应用的步骤就是

  1. 定义action和与之对应的reducer
  2. 监听store的变化,提供回调函数
  3. dispatch一个action,等待好运发生。

结合react,其他view类库,开发步骤莫不如此。

高级应用

异步action

我们也看到了,我们的reducer只能做同步应用,如果我们需要在reducer,做一些延迟操作,可怎么办

社区已经有成熟的类库做这件事件

npm install redux-thunk --save

redux本身已经提高了很好的扩展机制,就是中间件。这点很类似express的中间件。

//引入新的类库
import { createStore, combineReducers, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'

...
//store部分做如下修改
const finalCreateStore = compose(applyMiddleware(thunk))(createStore)
const store = finalCreateStore(rootReducer, {})

redux-thunk的作用就是让dispatch方法不仅仅只接收action对象,还可以包含一个方法。我们可以在这个方法内部去调用异步代码

我们把dom事件部分做了如下改造

userNameButton.onclick = function() {
  var value = $userNameInput.value
  store.dispatch<any>(function(dispatch, getState) {
    setTimeout(() => {
      dispatch({
        type: 'CHANGE_USER_NAME',
        name: value
      })
    }, 2000)

  })
}

可以看到页面元素确实在2s之后发生了变化,实际业务中啊,我们这里可以做一些异步操作。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 一步一步创建vue2.0项目(一)

    新建一个文件夹 vue2.0-learn 。_前提是默认已经安装了nodejs和npm_

    frontoldman
  • (译)一篇对css网格布局的介绍

    css3 Grid Layout 表格布局是在css中强大的难以置信的布局模块。它是二维空间的,所以它可以处理行和列

    frontoldman
  • nodejs之http-proxy几点常见问题

    http-proxy是一个nodejs的http代理库,已经被webpack-dev-server集成进来,做代理使用。原因是在前后端分离大行其道的今天,我们如...

    frontoldman
  • Python爬虫入门教程 38-100 教育部高校名单数据爬虫 scrapy

    今天要爬取一下正规大学名单,这些名单是教育部公布具有招生资格的高校名单,除了这些学校以外,其他招生的单位,其所招学生的学籍、发放的毕业证书国家均不予承认,也就是...

    梦想橡皮擦
  • redux

    Redux 是 JavaScript 状态容器,提供可预测化的状态管理。可以让你构建一致化的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。R...

    _kyle
  • # 一篇文章让你搞懂如何在K8s 里使用 Traefik 2.0作为Ingress Controller(上)

    了解K8s的同学应该都知道,如果想要把应用暴露到公网上供外部访问,那么不可避免的会接触到Ingress资源。本文以Traefik为例,让大家对Ingress和T...

    100000798482
  • AkShare-股票数据-股票列表

    AkShare
  • FastDFS蛋疼的集群和负载均衡(十)之编写FastDFSUtil

    用户2032165
  • 一篇文章让你搞懂K8s Ingress,Traefik 2.0为例(上)

    了解K8s的同学应该都知道,如果想要把应用暴露到公网上供外部访问,那么不可避免的会接触到Ingress资源。本文以Traefik为例,让大家对Ingress和T...

    100000798482
  • 如何构造jvm的堆溢出和栈溢出

    首先,我们知道Java堆内存存放的是对象实例。所以原理上只要我们不断创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清楚这些对象,也就是...

    用户1637228

扫码关注云+社区

领取腾讯云代金券