美文网首页程序React常用知识
redux简介(一)基本使用

redux简介(一)基本使用

作者: 前端开发爱好者 | 来源:发表于2019-03-22 00:10 被阅读90次

    写在开头

    本片内容主要为本人在阅读redux官方文档中基础和进阶部分的学习笔记。由于本人能力有限,所以文章中可能会存在不合理的地方,欢迎指正,内容也会迭代更新。
    本篇为介绍redux的第一篇,主要介绍了基本的使用方法。

    前言

    redux是一个JavaScript状态容器,主要提供状态管理。可以运行于服务端,客户端,原生应用。除了支持React外还支持其他的UI框架,体积小,只有2kb。

    redux受Elm的启发,从Flux演变而来。简单容易上手。

    redux由于行为可以预测,所以可以实现,日志打印,热加载,时间旅行,同构应用,录制和重放等,不需要开发参与。

    开始使用

    • 确认所开发的程序是否需要使用redux。对于较为简单的应用,完全没有必要为了使用redux而使用redux,redux会增加代码的复杂度。
    • 参考资料你

    安装

    npm install redux --save
    npm install redux-thunk --save
    npm install redux-logger --save
    npm install react-redux --save
    npm install redux-devtools --save-dev
    
    npm i redux redux-thunk redux-logger --save
    // redux-thunk用于支持异步的action,redux-logger用于打印log,主要用于在开发阶段。react-redux用于和react结合使用
    

    问什么要使用redux

    • 使用JavaScript开发的单页应用逻辑复杂。有较多的状态(state)需要JavaScript去维护。例如:服务器返回数据,本低为持久化的数据,UI状态。或者分页等等。
    • 将变化和异步混合在一起会增加程序的复杂性,使得状态难以被管理。React在视图层禁止异步不鼓励直接操作DOM,来解决这个问题。不过react中的数据(state)需要自己来进行处理,redux就是为了解决这个问题
    • Redux试图让state的变化变得可预测。

    redux的核心

    使用state来存储数据,只能触发action通过reducer来更改state。

    redux的三大原则

    1. 单一数据源:整个应用的state被存储在一颗object tree中,并且这个object只存在与唯一的一个store中
    2. state是只读的:改变state的唯一方法就是触发action,action是用于米哦啊书已发生时间的普通对象。
    3. 使用纯函数来执行修改:为了描述action如何改变state tree,需要编写 reducers

    先前技术

    链接

    redux生态系统

    中间件

    • redux-thunk — 用最简单的方式搭建异步
    • action 构造器
    • redux-promise — 遵从 FSA 标准的 promise 中间
    • redux-axios-middleware — 使用 axios HTTP 客户端获取数据的 Redux 中间件
    • redux-observable — Redux 的 RxJS 中间件
    • redux-rx — 给 Redux 用的 RxJS 工具,包括观察变量的中间件
    • redux-logger — 记录所有 Redux action 和下一次 state 的日志
    • redux-immutable-state-invariant — 开发中的状态变更提醒
    • redux-unhandled-action — 开发过程中,若 Action 未使 State 发生变化则发出警告
    • redux-analytics — Redux middleware 分析
    • redux-gen — Redux middleware 生成器
    • redux-saga — Redux 应用的另一种副作用 model
    • redux-action-tree — Redux 的可组合性 Cerebral-style 信号
    • apollo-client — 针对 GraphQL 服务器及基于 Redux 的 UI 框架的缓存客户端

    路由

    • redux-simple-router — 保持 React Router 和 Redux 同步
    • redux-router — 由 React Router 绑定到 Redux 的库

    更多

    基础知识

    Action

    • action用于将数据传入store。本质是普通的JavaScript对象,必须含有type字段。
    • 应该尽量减少在action中传递数据。
    const ADD_TODO = 'ADD_TODO';
    {
        type: ADD_TODO,
        text: 'Build my first Redux app'
    }
    

    使用函数创建Action

    const ADD_TODO = 'ADD_TODO';
    function addTodo(text) {
      return {
        type: ADD_TODO,
        text
      }
    }
    

    Action创建函数可以是异步非纯函数。
    在store里能直接通过store.dispatch()调用dispatch()方法。但是在redux应用中通常会使用connect()帮助器来调用,启动bindActionCreators()可以自动把多个action创建的函数绑定到dispatch()方法上。

    Reducer

    设计state结构

    • state范式化,不要存在嵌套把state比拟成数据库。进行适当的数据扁平化。参考normalizr

    reducer

    • reducer接受旧的satte和action,返回新的action
    (previousState, action) => newState
    

    reducer名字的由来是因为此函数与Array.prototype.reduce(reducer,?initiaValue)里的回调函数类型相似。

    reducer禁止的操作

    • 修改传入的参数
    • 执行有副作用操作,Ajax请求,路由跳转等
    • 调用非纯函数:Date.now()或Math.random()

    慎记:只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算

    import { VisibilityFilters } from './actions'
    
    const initialState = {
      visibilityFilter: VisibilityFilters.SHOW_ALL,
      todos: []
    };
    
    function todoApp(state = initialState, action) {
      switch (action.type) {
        case SET_VISIBILITY_FILTER:
          return Object.assign({}, state, {
            visibilityFilter: action.filter
          })
        default:
          return state
      }
    }
    

    注意

    1. 不要修改state。使用Object.assign({},state,newState),或者{...state,...newState}
    2. 在default情况下返回旧的state。

    拆分reducer
    注意每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据。

    使用combineReducers()

    import { combineReducers } from 'redux'
    
    const todoApp = combineReducers({
      visibilityFilter,
      todos
    })
    

    等价

    function todoApp(state = {}, action) {
      return {
        visibilityFilter: visibilityFilter(state.visibilityFilter, action),
        todos: todos(state.todos, action)
      }
    }
    

    combineReducers()生成一个函数,这个函数来调用你的一系列 reducer,每个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理,然后这个生成的函数再将所有 reducer 的结果合并成一个大的对象。没有任何魔法。正如其他 reducers,如果 combineReducers() 中包含的所有 reducers 都没有更改 state,那么也就不会创建一个新的对象。

    es6 使用import *

    import { combineReducers } from 'redux'
    import * as reducers from './reducers'
    
    const todoApp = combineReducers(reducers)
    

    Store

    职责
    • 维持应用的state
    • 提供 getState() 方法获取 state;
    • 提供 dispatch(action) 方法更新 state;
    • 通过 subscribe(listener) 注册监听器;
    • 通过 subscribe(listener) 返回的函数注销监听器。
    创建store
    import { createStore } from 'redux'
    import todoApp from './reducers'
    let store = createStore(todoApp)
    
    let store = createStore(todoApp, window.STATE_FROM_SERVER)
    
    let store = createStore(todoApp, window.STATE_FROM_SERVER,applyMiddleware())
    
    发起Actions
    import {
      addTodo,
      toggleTodo,
      setVisibilityFilter,
      VisibilityFilters
    } from './actions'
    
    // 打印初始状态
    console.log(store.getState())
    
    // 每次 state 更新时,打印日志
    // 注意 subscribe() 返回一个函数用来注销监听器
    const unsubscribe = store.subscribe(() =>
      console.log(store.getState())
    )
    
    // 发起一系列 action
    store.dispatch(addTodo('Learn about actions'))
    
    // 停止监听 state 更新
    unsubscribe();
    

    数据流

    严格的单向数据流是 Redux 架构的设计核心
    Redux 应用中数据的生命周期遵循下面 4 个步骤。

    1. 调用 store.dispatch(action)
    2. Redux store 调用传入的 reducer 函数。
    3. 根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树。
    4. Redux store 保存了根 reducer 返回的完整 state 树。

    进阶

    异步Action

    需求:发送异步的Action

    • 一种通知reducer请求开始的action
    • 一种通知reducer请求成功的action
    • 一种通知action请求失败的action

    实现

    export const REQUEST_POSTS = 'REQUEST_POSTS';
    export const RECEIVE_POSTS = 'RECEIVE_POSTS';
    export const INVALIDATE_SUBREDDIT = 'INVALIDATE_SUBREDDIT';
    
    
    function requestPosts(subreddit) {
        return {
            type: REQUEST_POSTS,
            subreddit
        }
    }
    
    function receivePosts(subreddit, json) {
        return {
            type: RECEIVE_POSTS,
            subreddit,
            posts: json.data.children.map(child => child.data),
            receivedAt: Date.now()
        }
    }
    
     export function invalidateSubreddit(subreddit) {
         return {
             type: INVALIDATE_SUBREDDIT,
             subreddit
         }
     }
    
    // 来看一下我们写的第一个 thunk action 创建函数!
    // 虽然内部操作不同,你可以像其它 action 创建函数 一样使用它:
    // store.dispatch(fetchPosts('reactjs'))
    
    export function fetchPosts(subreddit) {
    
        // Thunk middleware 知道如何处理函数。
        // 这里把 dispatch 方法通过参数的形式传给函数,
        // 以此来让它自己也能 dispatch action。
    
        return function (dispatch) {
    
            // 首次 dispatch:更新应用的 state 来通知
            // API 请求发起了。
    
            dispatch(requestPosts(subreddit))
            //可以多次dispatch
            // thunk middleware 调用的函数可以有返回值,
            // 它会被当作 dispatch 方法的返回值传递。
    
            // 这个案例中,我们返回一个等待处理的 promise。
            // 这并不是 redux middleware 所必须的,但这对于我们而言很方便。
    
            return fetch(`http://www.subreddit.com/r/${subreddit}.json`)
                .then(
                    response => response.json(),
                    // 不要使用 catch,因为会捕获
                    // 在 dispatch 和渲染中出现的任何错误,
                    // 导致 'Unexpected batch number' 错误。
                    // https://github.com/facebook/react/issues/6895
                    error => console.log('An error occurred.', error)
                )
                .then(json =>
                    // 可以多次 dispatch!
                    // 这里,使用 API 请求结果来更新应用的 state。
    
                    dispatch(receivePosts(subreddit, json))
                )
        }
    }
    

    引入thunk函数

    import thunkMiddleware from 'redux-thunk'
    import {createLogger} from 'redux-logger'
    import {createStore, applyMiddleware} from 'redux'
    import {selectSubreddit, fetchPosts} from './actions'
    import rootReducer from './reducers'
    
    const store = createStore(
        rootReducer,
        applyMiddleware(
            thunkMiddleware, // 允许我们 dispatch() 函数
            createLogger() // 一个很便捷的 middleware,用来打印 action 日志
        )
    );
    
    store.dispatch(selectSubreddit('reactjs'));
    store.dispatch(fetchPosts('reactjs')).then(
        () => console.log(store.getState())
    );
    

    除了redux-thunk不是处理异步action的唯一方式。

    • 你可以使用 redux-promise 或者 redux-promise-middleware 来 dispatch Promise 来替代函数。
    • 你可以使用 redux-observable 来 dispatch Observable。
    • 你可以使用 redux-saga 中间件来创建更加复杂的异步 action。
    • 你可以使用 redux-pack 中间件 dispatch 基于 Promise 的异步 Action。
    • 你甚至可以写一个自定义的 middleware 来描述 API 请求,就像这个 真实场景的案例 中的做法一样

    异步数据流

    默认情况下使用createStore()创建的只支持同步数据流,不过可以通过applyMiddleware()来增强createStore()以使其支持异步action。
    redux-thunk或redux-promise通过封装store.dispatch()来实现对于异步的支持。使用middleware可以以自定义的方式解决dispatch的任何内容,并传递action给下一个middleware。支持Promise的middleware能够拦截Promise,然后为每个Promsie异步地dispatch一对begin/end。
    需要确保middleware最后一个middleware开始dispatch action,这个action必须是一个普通代码。

    Middleware

    Redux middleware提供的是位于action被发现之后,到达reducer之前的扩展点。

    Middleware 接收了一个 next() 的 dispatch 函数,并返回一个 dispatch 函数,返回的函数会被作为下一个 middleware 的 next(),以此类推。由于 store 中类似 getState() 的方法依旧非常有用,我们将 store 作为顶层的参数,使得它可以在所有 middleware 中被使用。

    /**
     * 让你可以发起一个函数来替代 action。
     * 这个函数接收 `dispatch` 和 `getState` 作为参数。
     *
     * 对于(根据 `getState()` 的情况)提前退出,或者异步控制流( `dispatch()` 一些其他东西)来说,这非常有用。
     *
     * `dispatch` 会返回被发起函数的返回值。
     */
    const thunk = store => next => action =>
      typeof action === 'function' ?
        action(store.dispatch, store.getState) :
        next(action)
    

    技巧

    1.使用对象展开运算符
    2.缩减样板代码
    Actions

    action需要拥有一个不变的type帮助reducer识别他们,因为string是可序列化的,使用Symbol()会使纪录和重载变得困难,所以推荐使用string
    将每个action type定义为string常量。

    • 维护命名的一致性
    • 避免冲突
    • 及时追踪新功能的范围和实现
    • 减少由于书写造成的错误的可能性
    Action Creators
    // event handler 里的某处
    dispatch({
      type: 'ADD_TODO',
      text: 'Use Redux'
    });
    
    export function addTodo(text) {
      return {
        type: 'ADD_TODO',
        text
      };
    }
    
    // event handler 里的某处
    dispatch(addTodo('Use Redux'))
    

    使用Action creators的优势。

    例如,最多只允许三个todo

    function addTodoWithoutCheck(text) {
      return {
        type: 'ADD_TODO',
        text
      };
    }
    
    export function addTodo(text) {
      // Redux Thunk 中间件允许这种形式
      // 在下面的 “异步 Action Creators” 段落中有写
      return function (dispatch, getState) {
        if (getState().todos.length === 3) {
          // 提前退出
          return;
        }
    
        dispatch(addTodoWithoutCheck(text));
      }
    }
    

    Action creator 可以解耦额外的分发 action 逻辑与实际发送这些 action 的 components的耦合。

    ActionCreators生成器

     export function addTodo(text) {
      return {
        type: 'ADD_TODO',
        text
      }
    }
    
    export function editTodo(id, text) {
      return {
        type: 'EDIT_TODO',
        id,
        text
      }
    }
    
    export function removeTodo(id) {
      return {
        type: 'REMOVE_TODO',
        id
      }
    }
    
    function makeActionCreator(type, ...argNames) {
      return function(...args) {
        let action = { type }
        argNames.forEach((arg, index) => {
          action[argNames[index]] = args[index]
        })
        return action
      }
    }
    
    const ADD_TODO = 'ADD_TODO'
    const EDIT_TODO = 'EDIT_TODO'
    const REMOVE_TODO = 'REMOVE_TODO'
    
    export const addTodo = makeActionCreator(ADD_TODO, 'todo')
    export const editTodo = makeActionCreator(EDIT_TODO, 'id', 'todo')
    export const removeTodo = makeActionCreator(REMOVE_TODO, 'id')
    
    异步Action Creators

    中间件 让你在每个 action 对象 dispatch 出去之前,注入一个自定义的逻辑来解释你的 action 对象
    中间件让我们能写表达更清晰的、潜在的异步 action creators。允许我们 dispatch 普通对象之外的东西,并且解释它们的值。比如,中间件能 “捕捉” 到已经 dispatch 的 Promises 并把他们变为一对请求和成功/失败的 action。

    redux-thunk. “Thunk” 中间件让你可以把 action creators 写成 “thunks”,也就是返回函数的函数。 这使得控制被反转了: 你会像一个参数一样取得 dispatch ,所以你也能写一个多次分发的 action creator 。

    export function loadPosts(userId) {
      // 用 thunk 中间件解释:
      return function (dispatch, getState) {
        let { posts } = getState();
        if (posts[userId]) {
          // 这里是数据缓存!啥也不做。
          return;
        }
    
        dispatch({
          type: 'LOAD_POSTS_REQUEST',
          userId
        });
    
        // 异步分发原味 action
        fetch(`http://myapi.com/users/${userId}/posts`).then(
          response => dispatch({
            type: 'LOAD_POSTS_SUCCESS',
            userId,
            response
          }),
          error => dispatch({
            type: 'LOAD_POSTS_FAILURE',
            userId,
            error
          })
        );
      }
    }
    

    未避免多次重复代码,使用middleware进行处理。

    Reducers生成器

    写一个函数将 reducers 表达为 action types 到 handlers 的映射对象。例如,如果想在 todos reducer 里这样定义:

    export const todos = createReducer([], {
      [ActionTypes.ADD_TODO](state, action) {
        let text = action.text.trim();
        return [...state, text];
      }
    })
    

    可以编写下面的辅助函数来完成:

    function createReducer(initialState, handlers) {
      return function reducer(state = initialState, action) {
        if (handlers.hasOwnProperty(action.type)) {
          return handlers[action.type](state, action);
        } else {
          return state;
        }
      }
    }
    

    鉴于写法多种多样,Redux 没有默认提供这样的辅助函数

    写在最后

    谢谢阅读,欢迎点赞,

    推荐阅读原版英文/中文文档。在可以灵活使用的基础上,下载源码进行阅读学习,相信会有很多的收获,加油 💪💪💪

    相关文章

      网友评论

        本文标题:redux简介(一)基本使用

        本文链接:https://www.haomeiwen.com/subject/vqrkvqtx.html