Redux 基础教程

前言

在没有使用 ReduxReact 项目中的组件通讯和状态管理是特别繁琐的,好比子组件和父组件通讯改变值,要经过父组件的方法。使用 Redux 能够解决组件之间的通讯问题,由于 React 提出将展现组件与容器组件分离的思想,因此下降了 ReactRedux之间的耦合度。不少人都说,简单的应用能够不用此工具。可是我我的认为,中小型应用使用的话,可使文件结构更加规范,代码可读性更强。javascript

做用

  1. 分离UI与逻辑
  2. 跨组件通讯

安装

npm i redux -Sjava

核心概念

1. state

单一数据源原则:整个应用的 state 数据都被储存在一棵 object tree 中,而且这个 object tree 只存在于惟一一个 store 中。node

state 其实就是一个普通的 JSON 对象。该对象包含 redux 的全部数据。react

当前的 state,能够经过 store.getState() 获取。npm

Redux 规定, 一个 State 对应一个 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什么样,反之亦然。redux

建议定义 state tree 层级结构不要太深。bash

{
  text: 'text 文本',
  todos: [1,2,3,4]
}
复制代码

2. action

State 只读原则:惟一改变 state 的方法就是触发 actionaction 是一个用于描述已发生事件的普通对象。dom

咱们约定:action 内必须使用一个字符串类型的 type 字段来表示将要执行的动做。一般其余参数用 payload 字段来存储以便管理方便,结构清晰。异步

const ADD_TODO = 'ADD_TODO'

// action
{
  type: ADD_TODO,
  payload: {
    text: 'Build my first action'
  }
}
复制代码

3. Action 建立函数

Action 建立函数其实就是生成 action 的方法。“action” 和 “action 建立函数” 这两个概念很容易混在一块儿,使用时最好注意区分。ide

有时候改变多少消息是,就会有多少个 action。若是每次都写一遍相似的操做,就会显得繁琐、麻烦。咱们能够经过定义一个函数来生成一个 Action,这个函数就叫 Action 建立函数。

const updateText = 'updateText'

// action 建立函数
function addTodo(text) {
  return {
    type: updateText,
    payload: {
      text,
    }
  }
}

const action = addTodo('Action Creator');
复制代码

4. Reducer

使用纯函数修改state原则:为了描述 action 如何改变 state tree ,你须要编写 reducers

Reducer 是一个纯函数,只能经过使用Reducer 用来修改 state tree 的数据。

Reducer 是接收 stateaction 参数,返回新的 state 的一个函数。

Reducer 必须遵照的约束条件:

  1. 修改传入参数
  2. 执行有反作用的操做,如 API 请求和路由跳转
  3. 调用非纯函数,如 Date.now()Math.random(),由于每次会获得不同的结果,没法预测 Reducer 执行后的结果。
(previousState, action) => newState
复制代码
const initialState = {
  text: 'text 文本',
  todos: [1,2,3,4]
};
function updateText(state = initialState, action) {
  switch (action.type) {
    case 'updateText':
      return Object.assign({}, state, {
        text: action.payload.text
      })
    default:
      return state
  }
}
复制代码

5. Store

使用 action 来描述“发生了什么”,和使用 reducers 来根据 action 更新 state 的用法。Store 就是把它们联系到一块儿的对象。Store 有如下职责:

  1. 维持应用的 state
  2. 提供 getState() 方法获取 state
  3. 提供 dispatch(action) 方法更新 state
  4. 经过 subscribe(listener) 注册监听器
  5. 经过 subscribe(listener) 返回的函数注销监听器

工做流程

  1. 用户调用 store.dispatch(action) 触发 Action
  2. 接着 Store 调用传入的 reducer 函数(此过程传入当前的 state tressaction
  3. 而后 reducer 计算下一个 state 返回给 Store 并更新 state tree
  4. 最后 Store 根据新的 state tree 从新渲染 View

React 集成 Redux

1. 介绍

前面咱们介绍了 redux 的核心概念和工做流程,接着咱们介绍 React 集成 Redux 功能。

首先,安装 ReduxReact 绑定库:

npm i react-redux -S

为了让页面的全部容器组件均可以访问 Redux store,因此能够手动监听它。

一种方式是把它以 props 的形式传入到全部容器组件中。但这太麻烦了,由于必需要用 store 把展现组件包裹一层,仅仅是由于刚好在组件树中渲染了一个容器组件。

还有就是使用 React Redux 组件 <Provider> 来让全部容器组件均可以访问 store,而没必要显示地传递它。(推荐使用)。

2. 纯 Redux 例子

这里须要再强调一下: ReduxReact 之间没有关系。Redux 支持 React、Angular、Ember、jQuery 甚至纯 JavaScript

尽管如此,Redux 仍是和 ReactDeku 这类库搭配起来用最好,由于这类库容许你以 state 函数的形式来描述界面,Redux 经过 action 的形式来发起 state 变化。

这里咱们介绍一下单纯使用 Redux 来实现其用法:

  1. store 管理
reduxDemo/store.ts
复制代码
import { createStore, Store, Reducer } from 'redux';

interface IState {
  num: number;
}

const initState = {
  num: 1,
};

const reducers: Reducer<IState> = (state = initState, action) => {
  switch (action.type) {
    case 'add':
      return {
        ...state,
        num: state.num + 1,
      };
    case 'minus':
      return {
        ...state,
        num: state.num - 1,
      };
    default:
      return state;
  }
};

const store: Store<IState> = createStore(reducers);

export default store;

复制代码
  1. 组件
reduxDemo/index.tsx
复制代码
import * as React from 'react';
import { Unsubscribe } from 'redux';
import store from './store';

class Index extends React.PureComponent {
  subscribe?: Unsubscribe;

  componentDidMount(): void {
    this.subscribe = store.subscribe(() => {
      this.forceUpdate();
    });
  }

  componentWillUnmount(): void {
    if (this.subscribe) {
      this.subscribe();
    }
  }

  render(): JSX.Element {
    return (
      <div>
        <div>{store.getState().num}</div>
        <button type="button" onClick={() => store.dispatch({ type: 'add' })}>加一</button>
        <button type="button" onClick={() => store.dispatch({ type: 'minus' })}>减一</button>
      </div>
    );
  }
}

export default Index;

复制代码

3. 使用 react-redux 例子

经过上面的例子咱们看到单纯在 React 项目中使用 Redux,咱们还须要手动监听 Storestate 的变化和取消监听,而后在手动进行强制更新从新渲染组件。

接着咱们举例使用在 React 项目中使用 react-redux 绑定库例子:

  1. 展现组件

纯UI展现组件,没有任何交互逻辑,并不关心数据来源和如何改变。

components/Todo.tsx
复制代码
import React from 'react';

interface IProps {
  text: string;
  completed: boolean;
  onClick?: (e: React.MouseEvent) => void;}

export default function Todo({
  onClick,
  completed,
  text,
}: IProps): JSX.Element {
  return (
    <li
      onClick={onClick}
      style={{
        textDecoration: completed ? 'line-through' : 'none',
      }}
    >
      {text}
    </li>
  );
}
复制代码
components/TodoList.tsx
复制代码
import React from 'react';
import Todo from './Todo';

interface IProps {
  todos: any[];
  onTodoClick: (id: number) => void;
}

export default function TodoList({
  todos,
  onTodoClick,
}: IProps): JSX.Element {
  return (
    <ul>
      {todos.map((todo) => (
        <Todo key={todo.id} {...todo} onClick={() => onTodoClick(todo.id)} />
      ))}
    </ul>
  );
}
复制代码
components/Link.tsx
复制代码
import React from 'react';

interface IProps {
  active: boolean;
  children: any;
  onClick: () => void;
}

export default function Link({
  active,
  children,
  onClick,
}: IProps): JSX.Element {
  if (active) {
    return <span>{children}</span>;
  }

  return (
    <a
      href="javascript(0);"
      onClick={(e) => {
        e.preventDefault();
        onClick();
      }}
    >
      {children}
    </a>
  );
}
复制代码
components/Footer.tsx
复制代码
import React from 'react';
import FilterLink from '../containers/FilterLink';

const Footer = (): JSX.Element => (
  <p>
    Show:
    {' '}
    <FilterLink filter="SHOW_ALL">
      All
    </FilterLink>
    {', '}
    <FilterLink filter="SHOW_ACTIVE">
      Active
    </FilterLink>
    {', '}
    <FilterLink filter="SHOW_COMPLETED">
      Completed
    </FilterLink>
  </p>
);

export default Footer;
复制代码
  1. 容器组件

展现组件 链接到 Redux

containers/FilterLink.tsx
复制代码
import { connect } from 'react-redux';
import { setVisibilityFilter, namespace } from '../actions';
import Link from '../components/Link';

const mapStateToProps = (state: any, ownProps: any): object => ({
  active: ownProps.filter === state[namespace].visibilityFilter,
});

const mapDispatchToProps = (dispatch: any, ownProps: any): object => ({
  onClick: () => {
    dispatch(setVisibilityFilter(ownProps.filter));
  },
});

const FilterLink = connect(
  mapStateToProps,
  mapDispatchToProps,
)(Link);

export default FilterLink;
复制代码
containers/VisibleTodoList.tsx
复制代码
import { connect } from 'react-redux';
import { toggleTodo, namespace } from '../actions';
import TodoList from '../components/TodoList';

const getVisibleTodos = (todos: any[], filter: string): any[] => {
  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: any): { todos: any[] } => ({
  todos: getVisibleTodos(state[namespace].todos, state[namespace].visibilityFilter),
});

interface TDispatchProps {
  onTodoClick: (id: number) => void;
}

const mapDispatchToProps = (dispatch: any): TDispatchProps => ({
  onTodoClick: (id: number) => {
    dispatch(toggleTodo(id));
  },
});

const VisibleTodoList = connect<
  {
    todos: any[];
  },
  TDispatchProps,
  any
>(
  mapStateToProps,
  mapDispatchToProps,
)(TodoList);

export default VisibleTodoList;
复制代码
containers/AddTodo.tsx
复制代码
import React from 'react';
import { connect } from 'react-redux';
import { addTodo } from '../actions';

interface IProps {
  onAddClick: (text: string) => void;
}

export const AddTodo = ({ onAddClick }: IProps): JSX.Element => {
  let input: any = null;

  return (
    <div>
      <form
        onSubmit={(e) => {
          e.preventDefault();
          if (!input.value.trim()) {
            return;
          }
          onAddClick(input.value);
          input.value = '';
        }}
      >
        <input
          ref={(node) => {
            input = node;
          }}
        />
        <button type="submit">Add Todo</button>
      </form>
    </div>
  );
};

export default connect(undefined, (dispatch: any) => ({
  onAddClick: (text: string) => dispatch(addTodo(text)),
}))(AddTodo);
复制代码
  1. action

书写须要的 action

let nextTodoId = 2;

export const namespace = 'pages/index';

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

export const addTodo = (text: string): object => {
  nextTodoId += 1;
  return {
    type: ADD_TODO,
    payload: {
      id: nextTodoId,
      text,
    },
  };
};

export const setVisibilityFilter = (filter: string): object => ({
  type: SET_VISIBILITY_FILTER,
  payload: {
    filter,
  },
});

export const toggleTodo = (id: number): object => ({
  type: TOGGLE_TODO,
  payload: {
    id,
  },
});

复制代码
  1. Reducer

书写 Reducer 更新 ```state tree``。

import { ReducerBuildOptions } from '@src/redux/reducer/typings';
import {
  ADD_TODO,
  TOGGLE_TODO,
  SET_VISIBILITY_FILTER,
  namespace,
} from './actions';

export interface ModalState {
  visibilityFilter: string;
  todos: {
    id: number;
    text?: string;
    completed?: boolean;
  }[];
}

export const initialState: ModalState = {
  visibilityFilter: 'SHOW_ALL',
  todos: [
    {
      id: 1,
      text: 'Consider using Redux',
      completed: true,
    },
    {
      id: 2,
      text: 'Keep all state in a single tree',
      completed: false,
    },
  ],
};

export interface ModalReducers {
  updateState: ReducerBuildOptions<ModalState, ModalState>;
  [TOGGLE_TODO]: ReducerBuildOptions<ModalState, { id: number }>;
  [ADD_TODO]: ReducerBuildOptions<ModalState, { id: number; text: string }>;
  [SET_VISIBILITY_FILTER]: ReducerBuildOptions<ModalState>;
}

const reducers: ModalReducers = {
  updateState: (state, { payload }) => ({ ...state, ...payload }),
  [TOGGLE_TODO]: (state, { payload }) => {
    const { todos } = state;

    return {
      ...state,
      todos: todos.map((todo) => (todo.id === payload?.id
        ? {
          ...todo,
          completed: !todo.completed,
        }
        : todo)),
    };
  },
  [ADD_TODO]: (state, { payload }) => ({
    ...state,
    todos: state.todos.concat({
      id: payload?.id || Math.random(),
      text: payload?.text,
      completed: false,
    }),
  }),
  [SET_VISIBILITY_FILTER]: (state, { payload }) => ({
    ...state,
    ...payload,
  }),
};

const createReducer = (initialState, handlers) =>(state = initialState, action){
  const { type } = action;

  if (type in handlers) {
    return handlers[type](state, action);
  }
  return state;
}

export default combineReducers({
  [namespace]: createReducer(initialState,reducers)
});
复制代码
  1. React 集成 Redux
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
// 你的 reducer 集合
import reducers from './reducers'
import App from './components/App'

let store = createStore(reducers)

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)
复制代码

后续的计划

  1. Redux 高级教程,其中包含异步流数据处理,动态读取 reducersaga
  2. 手写实现 Redux
  3. ...
相关文章
相关标签/搜索