手把手教你Redux

131 阅读5分钟

Redux

由js实现的flux思想第三方状态管理库,可以与react、vue、angular甚至jQuery结合使用

一、redux介绍及设计与使用三大原则

  • state以单一对象存储在store对象中
  • state只读(每次都返回一个新的对象)
  • 使用纯函数reducer执行state更新

纯函数定义

1、对外界没有副作用

2、同样的输入无论执行多少次都得到同样的输出

二、redux 工作流

点击查看图片来源

三、redux用法

创建store
//store/redux.js
import { createStore } from "redux";
// 处理函数
const reducer = (prevState = { show: true }, action) => {
  let newState = { ...prevState };
  switch (action.type) {
    case "show":
      newState.show = true;
      return newState;
      break;
    case "hide":
      newState.show = false;
      return newState;
      break;
    default:
      return prevState;
      break;
  }
};
​
const store = createStore(reducer);
​
export default store;
store发布
import store from '../store/redux'useEffect(() => {
  // dispatch action对象
  store.dispatch({
    type: "show",
  });
  return () => {
    store.dispatch({
      type: "hide",
    });
  };
}, []);
store订阅和取消订阅
import store from "../store/redux";
​
const [showState, setShowState] = setState();
//执行完后返回取消订阅的函数
let unsubsribe = store.subsribe(() => {
  //转化为自己的状态
  const { show } = store.getState();
  setShowState(show);
});
//取消订阅
unsubsribe()

四、redux基本原理

基本原理是发布订阅模式,通过reducer更新状态

function createStore(reducer) {
  let list = [];
  let state = reducer(undefined, {});
  function dispatch(action) {
    // 新状态       老状态
    state = reducer(state, action);
    for (let i in list) {
      list[i] && list[i]();
    }
  }
  function subsribe(callback) {
    list.push(callback);
  }
  function getState() {
    return state;
  }
  return {
    dispatch,
    subsribe,
    getState,
  };
}

五、reducer合并

如果不同的action所处理的属性之间没有联系,可以把Reducer函数拆分,最终合成一个大的Reducer

combineReducers
import ARdecuder from "./Rdecuders/ARdecuder";
import BRdecuder from "./Rdecuders/BRdecuder";
import { combineReducers } from "redux";
const reducer = combineReducers({
  ARdecuder,
  BRdecuder,
});
访问

要加上对应的reducer(不同的命名空间)

store.subsribe(() => {
  //store.getState(): {ARdecuder:{},BRdecuder:{}}
  const { show } = store.getState().ARdecuder;
  setShowState(show);
});

六、中间件(middleware)

用于在action和reducer中间架起一道桥梁来异步处理。

中间件的由来和机制
function thunkMiddleware({ dispatch, getState }) {
  return (next) => (aciton) => {
    typeof aciton === "function" ? aciton(dispatch, getState) : next(aciton);
  };
}

中间件接收到参数action,当action不是函数时就和之前一样执行next方法(下一步处理);是函数就先调用action函数,在action处理完成后就在内部调用dispatch。

常用异步中间件
redux-thunk

action参数需要是一个function。

安装redux-thunk

npm i redux-thunk

注册redux-thunk

import { createStore, applyMiddleware } from "redux";
import reduxThunk from "redux-thunk";
const store = createStore(reducer, applyMiddleware(reduxThunk));

dispatch action函数

import store from "../store/redux";
useEffect(() => {
  store.dispatch(getListAction());
}, []);

异步action

// getListAction.js
function getListAction() {
  axios.get("").then((res) => {
    store.dispatch({
      type: "data",
      data: res.data,
    });
  });
}

对所有reducer分发,最后匹配到对应reducer

// getListrReducer
function getListrReducer(prevState, aciton) {}
redux-promise

redux-promise唯一不同的是action需要是一个promise对象。redux-promise会自动dispatch。

安装redux-promise

npm i redux-promise

注册redux-promise

可以同时注册多个中间件,现在action可以是一个函数也可以是一个promise

import { createStore, applyMiddleware } from "redux";
import reduxThunk from "redux-thunk";
import reduxPromise from "redux-promise";
const store = createStore(reducer, applyMiddleware(reduxThunk,reduxPromise));

异步action需要返回一个promise

// 异步action promise
function getListAction() {
  return axios.get("").then((res) => {
    return {
      type: "data",
      data: res.data,
    };
  });
}
async function getListAction() {
  let list = await axios.get("").then((res) => {
    return {
      type: "data",
      data: res.data,
    };
  });
  return list;
}

七、redux开发者工具引入

redux-devtools

redux-devtools需要增加配置

import { createStore, applyMiddleware, compose } from 'redux';
import reduxThunk from "redux-thunk";
import reduxPromise from "redux-promise";

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer,composeEnhancers(
    applyMiddleware(reduxThunk,reduxPromise)
));

react-redux

react-redux通过Provider组件和connect,把redux的state和dispatch都映射到props,从而使得不需要用redux的API自己订阅getState和分发dispatch。原理是把组件变为UI组件,通过connect生成一个容器组件作为该组件的父组件,通过porps把数据传给该组件。

一、UI组件和容器组件

UI组件
  • 只负责UI的呈现,不带有任何业务逻辑
  • 没有状态
  • 所有数据都有参数props提供
  • 不使用任何redux的API
容器组件
  • 负责管理数据和业务逻辑,不负责UI的呈现
  • 带有内部状态
  • 使用redux的API

二、Provider和connect

react-redux提供Provider组件,可以让容器组件拿到state

//index.js
import store from './store';
import { Provider } from "react-redux";
import App from "./App.js";
import ReactDom from "react-dom";
ReactDom.render(
  <Provider store={store}>
    <App />
  </Provider>
);

组件里使用connect

//Detail.js
import getListAction from "./getListAction";
import { connect } from "react-redux";
function Detail(props) {
  // 需要用到多个porps的值作为依赖项要解构出来
  // 不用向redux订阅存到状态里,直接来props的
  let { list, change, getListAction, hide, isShow } = props;
  useEffect(() => {
    if (list.length == 0) {
      getListAction();
    }
    return () => {
      hide();
    };
  }, [list, getListAction]);
  return (
    <div>
      {isShow && <div>show</div>}
      <ul>
        {list.map((item) => {
          <li
            onClick={(item) => {
              change(item);
            }}
          >
            {item.name}
          </li>;
        })}
      </ul>
    </div>
  );
}
// 映射state为props
const mapStatetoProps = (state) => {
  return {
    isShow: state.isShow,
    list: state.list,
  };
};
// 映射dispatch为props
// 用对象包裹,可以是任何多个任何形式函数,也可以传参。调用时需要把参数传过滤
const mapDispatchtoProps = {
  getListAction,
  hide: () => {
    return {
      type: "hide",
    };
  },
  change(item) {
    return {
      type: "change",
      value: item,
    };
  },
};
//connect两个参数,不传写null,第一个是将来给孩子的属性,第二个是将来给孩子的函数
export default connect(mapStatetoProps, mapDispatchtoProps)(Detail);

三、HOC和context通信在react-redux底层中的应用

  • connect是HOC,高阶组件
  • Provider组件可以让容器拿到state,使用了context

HOC不仅仅是一个方法,确切说是一个组件工厂,获取低阶组件,生成高阶组件。它可以

  • 代码复用,代码模块化
  • 增删改props
  • 渲染劫持(修改样式等)

四、connect原理

function connect(callback, obj) {
  let value = callback();
  // 返回一个函数,参数是组件,把低阶组件转成高阶组件
  return (MyComponent) => {
    // 返回一个函数式组件,因为组件外部包了一层父组件,路由的相关属性被放到了这一层父组件
    return (props) => {
      return (
        <div>
          <MyComponent {...value} {...obj} {...props} />
        </div>
      );
    };
  };
}

五、redux持久化

redux-persist

浏览器刷新,redux存起来的数据就会消失,有必要对数据进行持久化

安装
npm i redux-persist -S
使用
//store.js
import ARdecuder from "./Rdecuders/ARdecuder";
import BRdecuder from "./Rdecuders/BRdecuder";
import { combineReducers, createStore, applyMiddleware } from "redux";
import { persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
import reduxThunk from "redux-thunk";

// storage存起来的key
const persistConfig = {
  key: "root",
  storage,
  whitelist: ["ARdecuder"], // 只有白名单的reducer会持久化
  //blacklist: ['navigation'] // 只有黑名单的reducer不会持久化
};

const reducer = combineReducers({
  ARdecuder,
  BRdecuder,
});
// 对reducer持久化
const persistedReducer = persistReducer(persistConfig, reducer);

const store = createStore(persistedReducer, applyMiddleware(reduxThunk));
// 生成持久化的store
let persistor = persistStore(store);

export { store, persistor };

在App.js使用也可以

//index.js
import { PersistGate } from "redux-persist/integration/react";
import { Provider } from "react-redux";
import App from "./App.js";
import ReactDom from "react-dom";
ReactDom.render(
  <Provider store={store}>
    <PersistGate loading={null} persistor={persistor}>
      <App />
    </PersistGate>
  </Provider>
);