一文快速上手 Redux

Posted GoldenaArcher

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文快速上手 Redux相关的知识,希望对你有一定的参考价值。

一文快速上手 Redux

来自 Redux 官网 上列的基础教程之一,是 egghead.io 上的,视频网址 境内能够打开,笔记在 github 上也有。

这里只节选了部分内容,旨在学完了之后就能直接用 store, actionreducer 的功能。

Redux 内容提炼

  • Redux 只有一个不可变的状态树

  • action 用来描述 Redux 的状态变化

  • reducer 去实现 Redux 的状态变化

  • dispatch 用于触发 action 去调度对应的 reducer 去对状态树进行更改

  • Redux 的状态树是不可变的,每次 reducer 实现后都会返回一个新的状态树

  • counter 的效果图:

    redux-counter

视频笔记

The Single Immutable State Tree

即 Redux 自身有 一个 核心的不可变状态树。

Describing State Changes with Actions

字面意思是使用 actions 来描述 Redux 的状态变化。

这描述的是 Redux 的另一个特性,无法被直接修改或写入数据,唯一能够改变状态树的方法是发送一个 action 去改变 Redux 的状态树。

英文的文档里描述这个行为为:dispatch an action.

一个 action 是一个 javascript 的对象,它用来描述想要发送的变化,必须要有一个属性:类型,类型多用字符串做为值,因为字符串可以被序列化。

{
  // 必须要有 type 属性
  type: "INCREMENT";
}

仅有类型属性是不够的,在开发情况下会提供一些额外的数据用来描述这个 action 应该做什么。

以将商品加入购物车为例,仅仅有 加入购物车 这个行为描述是不够的,至少还需要有商品 id:

{
    type: "ADD_TO_CART",
    productId: 123456
}

Pure and Impure Functions

即纯函数与非纯函数。

纯函数是不会引起任何副作用,并且不会操作传来的参数的函数,它接收值,返回操作过的值对应值让别的函数使用。并且,无论调用多少次,纯函数所返回的结果始终如一,具有幂等性:

// pure function
function square(x) {
  return x ** 2;
}

非纯函数时会造成副作用,例如说数据库或是网络方面的,的函数:

function updateValue(id, val) {
  let updatedValue = val;
  // 其他的操作
  updatedValue = "some calculated value";
  updateDB(id, someVal);
}

下一次调用这个函数的时候,是无法保证函数会返回同一个值。

Reducer Function

Redux 中的 Reducer 就是一个纯函数,它接收上一个状态,从 action 中传过来的类型,然后返回一个新的状态。

小结 1

Redux 具有三个特性:

  • 只有一个不可变的状态树
  • 使用 actions 去描述状态变化
  • 使用 纯函数 reducers 去修改状态变化

案例 1,写一个 Counter Reducer 及其测试

const counter = (state = 0, action) => {
  // ES6 新特性 state = 0 重置了下面这段代码
  //   if (typeof state === "undefined") {
  //     return 0;
  //   }

  if (action.type === "INCREMENT") {
    return state + 1;
  } else if (action.type === "DECREMENT") {
    return state - 1;
  }
  return state;
};

console.assert(counter(0, { type: "INCREMENT" }) === 1);
console.assert(counter(2, { type: "DECREMENT" }) === 1);
console.assert(counter(undefined, { type: "INCREMENT" }) === 1);
console.assert(counter(10, { type: "NON_EXISTED_TYPE" }) === 10);

store 函数:getState(), dispatch(), subscribe()

  • getState(), 返回当前的状态(state)
  • dispatch(), 发送一个 action 去修改状态
  • subscribe(), 注册一个回到函数到 redux store 上,当 action 被发送时就会被调用

案例

JavaScript 代码:

const counter = (state = 0, action) => {
  // 用 switch 代替 if/else
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
};

// html中调用了Redux的CDN
const { createStore } = Redux;

const store = createStore(counter);

const render = () => {
  document.body.innerHTML = store.getState();
};

store.subscribe(() => {
  render();
});
render();

document.addEventListener("click", () => {
  store.dispatch({ type: "INCREMENT" });
});

HTML 部分:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/redux@4.1.0/dist/redux.js"></script>
    <style>
      body {
        margin: 100px;
      }
    </style>
  </head>
  <body>
    <script src="./reducer.js"></script>
  </body>
</html>

Implementing Store from Scratch

从零开始实现一个 store,具体实现为:

const createStore = (reducer) => {
  let state;
  let listeners = [];

  const getState = () => state;

  const dispatch = (action) => {
    state = reducer(state, action);
    listeners.forEach((listener) => listener());
  };

  const subscribe = (listener) => {
    listeners.push(listener);
    return () => {
      listeners = listeners.filter((l) => l !== listener);
    };
  };

  dispatch({}); // dummy dispatch

  return { getState, dispatch, subscribe };
};

store()函数 的介绍部分可以得知 store()函数有以下几个特性:

  • 接收 reducers 作为参数
  • store()函数 会返回有 getState(), dispatch(), subscribe() 三个函数的对象,分别有返回当前状态、发送 actions 和订阅的功能

鉴于 subscribe()函数 能被调用多次,所以在实践中使用了数组去保存所有变化的监听器。每一次 subscribe()函数 被调用,被调用的监听器就会被推进数组中。

如之前提到的,调用 action 是唯一一个能够改变 redux 内部状态的方法,因此在 dispatch函数 中需要调用对应的 reducer 去重新计算和更新状态。在状态被更新后,需要通过调用监听器去通知所有变化过的监听器。

在所有的监听器被调用后,就需要取消订阅相应的监听器,这也是 filter()函数 被调用的原因,通过从数组中移除监听器的方法去取消订阅已经被调用过的监听器。

返回一个空的对象,我的理解是为了第一次创建对象时去调用一次 reducer,让它返回初始状态。

React Counter 案例

注意,因为案例中使用了 JSX 语法糖,因此需要在 HTML 文档中添加 babel 预编译,此时完整的 HTML 如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/redux@4.1.0/dist/redux.js"></script>
    <script
      src="https://unpkg.com/react@17/umd/react.production.min.js"
      crossorigin
    ></script>
    <script
      src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"
      crossorigin
    ></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
    <style>
      body {
        margin: 100px;
      }
    </style>
  </head>
  <body>
    <div class="root" id="root"></div>
    <script src="./reducer.js" type="text/babel"></script>
  </body>
</html>

如果不想添加 babel 预编译的话,也可以到 babel repl 上将 JSX 语法糖手动编译成 ES5 的 JavaScript。

JS 部分的代码是这样的:

const counter = (state = 0, action) => {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
};

const Counter = ({ value, onIncrement, onDecrement }) => (
  <div>
    <h1>{value}</h1>
    <button onClick={onIncrement}>+</button>
    <button onClick={onDecrement}>-</button>
  </div>
);

const { createStore } = Redux;
const store = createStore(counter);

const render = () => {
  ReactDOM.render(
    <Counter
      value={store.getState()}
      onIncrement={() =>
        store.dispatch({
          type: "INCREMENT",
        })
      }
      onDecrement={() =>
        store.dispatch({
          type: "DECREMENT",
        })
      }
    />,
    document.getElementById("root")
  );
};

store.subscribe(() => {
  render();
});
render();

其中,counter 的 reducer 还是最早实现的样子,createStore(counter) 部分创建实例也是上面讲过的内容,唯一新增的部分是将 DOM 的操作由原生的 JavaScript 改为了 React。

对于 React 创建的组件 <Counter /> 来说,value 就是当前 Redux 中的状态,也就是经过操作后的数值,onIncrementonDecrement 是随着 DOM 事件触发的 action。

const Counter = (...) => {...} 这一段语法是一个纯函数,它只负责接受传来的参数,并且将其呈现在页面上。

最后,subscribe() 函数订阅了 render()函数,因此每一次 Redux 的状态发生改变,它就会调用 render函数,重新对页面进行渲染。

以上是关于一文快速上手 Redux的主要内容,如果未能解决你的问题,请参考以下文章

一文让你快速上手 Mockito 单元测试框架

一文快速上手Logstash

一文带你快速上手Highcharts框架

❤️[前端学习]大数据全栈工程师之一文快速上手vue3❤️

❤️[前端学习]大数据全栈工程师之一文快速上手vue3❤️

一文带你了解怎样快速上手微信小程序开发