Redux 简单使用

3 篇文章 0 订阅
2 篇文章 0 订阅

Redux 简单使用

# 创建一个测试项目
npx create-react-app test-redux-demo
# 安装 redux
yarn add redux
// src/redux/store.ts
// 创建 store 文件
import { createStore } from 'redux';

// action 类型
const actionTypes = {
  ADD: "add",
  SUB: "subtract",
}

// 初始化 state
const initState = { count: 0 }

// reducer 函数
const reducer = (state = initState, action: { type: string, payload: number }) => {
  const type = action.type
  const { ADD, SUB } = actionTypes
  const { count } = state
  if (type === ADD) {
    return { count: count + action.payload }
  }
  if (type === SUB) {
    return { count: count - action.payload }
  }
  return state
}

// 导入 reducer 创建 store
const store = createStore(reducer)

// action 方法
export async function addAction(count: number) {
  store.dispatch({
    type: actionTypes.ADD,
    payload: count
  })
}

export function subAction(count: number) {
  store.dispatch({
    type: actionTypes.SUB,
    payload: count
  })
}

// 导出 store
export default store
// src/page/Todo.tsx

// 使用 store
import { useEffect, useState } from 'react';
import store, { addAction, subAction } from '../redux/store';

export default function Todo() {
  // 初始化 count
  const [count, setCount] = useState(store.getState().count)

  useEffect(() => {
    // 监听 state 更新
    store.subscribe(() => {
      setCount(store.getState().count)
    })
  }, [])
    
  // 渲染
  return <>
    {/*触发 action */}
    <button onClick={() => addAction(1)}>+</button>
    <div>{count}</div>
    <button onClick={() => subAction(1)}>-</button>
  </>
}

createStore 方法简单实现

const createStore = (reducer) => {
  // 内部 state
  let state;
  // 监听函数数组
  let listeners = [];
  
  // 返回内部 state
  const getState = () => state;
  
  // 执行 dispatch 传入 action
  const dispatch = (action) => {
    // 执行 reducer 将返回值赋给内部 state
    state = reducer(state, action);
    // 统一执行监听函数数组
    listeners.forEach(listener => listener());
  };

  // 通过 subscribe 添加监听函数数组
  const subscribe = (listener) => {
    listeners.push(listener);
    return () => {
      listeners = listeners.filter(l => l !== listener);
    }
  };

  // 执行 dispatch 初始化内部 state
  dispatch({});

  // 返回 getState, dispatch, subscribe
  return { getState, dispatch, subscribe };
};

使用 combineReducers 抽出 reducer

// 创建 src/redux/count.ts
import store from "./store"

const actionTypes = {
  ADD: "add",
  SUB: "subtract",
}

const initState = { count: 0 }

export async function addAction(count: number) {
  let n = await new Promise((resolve) => {
    let timeout = setTimeout(() => {
      clearTimeout(timeout)
      resolve(count)
    }, 1000)
  })

  store.dispatch({
    type: actionTypes.ADD,
    payload: n as number
  })
}

export function subAction(count: number) {
  store.dispatch({
    type: actionTypes.SUB,
    payload: count
  })
}

const reducer = (state = initState, action: { type: string, payload: number }) => {
  const type = action.type
  const { ADD, SUB } = actionTypes
  const { count } = state
  if (type === ADD) {
    return { count: count + action.payload }
  }
  if (type === SUB) {
    return { count: count - action.payload }
  }
  return state
}

export default reducer
// 改造 src/redux/store.ts
import { createStore, combineReducers } from 'redux';
// 导入 count 的函数
import countReducer from './count';

const reducer = combineReducers({
  // 声明 countState, 接收 reducer 返回值: { count: 0 }
  countState: countReducer
})

/**
 * 等同于
function reducer(state: any = { count: 0 }, action: { type: string; payload: number; }) {
  return {
    countState: countReducer(state, action),
  }
}
 */

const store = createStore(reducer)

export default store
// src/page/Todo.tsx 稍作调整
import { useEffect, useState } from 'react';
import store from '../redux/store';
import { addAction, subAction } from '../redux/count'

export default function Todo() {
  const storeCount = store.getState().countState.count

  const [count, setCount] = useState(storeCount)
  console.log(count)

  useEffect(() => {
    store.subscribe(() => {
      setCount(store.getState().countState.count)
    })
  }, [])

  return <>
    <button onClick={() => addAction(1)}>+</button>
    <div>{count}</div>
    <button onClick={() => subAction(1)}>-</button>
  </>
}

简单异步

// 改造一下 src/redux/count.ts
export async function addAction(count: number) {
  let n = await new Promise((resolve) => {
    let timeout = setTimeout(() => {
      clearTimeout(timeout)
      resolve(count)
    }, 1000)
  })

  store.dispatch({
    type: actionTypes.ADD,
    payload: n as number
  })
}

使用 applyMiddleware 添加中间件

// src/redux/store.ts
// 以 redux-logger 为例
// 安装 yarn add redux-logger
import { createStore, combineReducers, applyMiddleware } from 'redux';
import reducer from './count';
import logger from 'redux-logger'


const store = createStore(combineReducers({
  countState: reducer
}),
  // 注册中间件
  applyMiddleware(logger) // 可同时添加多个中间件用逗号隔开,logger 需在最后一个
)

export default store
// applyMiddleware
function applyMiddleware() {
  // 获取中间件
  for (var _len = arguments.length, middlewares = new Array(_len), _key = 0; _key < _len; _key++) {
    middlewares[_key] = arguments[_key];
  }

  return function (createStore) {
    return function () {
      var store = createStore.apply(void 0, arguments);
        
      var _dispatch = function dispatch() {
        throw new Error('Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.');
      };
	  // 处理参数
      var middlewareAPI = {
        getState: store.getState,
        dispatch: function dispatch() {
          return _dispatch.apply(void 0, arguments);
        }
      };
      // 处理中间件
      var chain = middlewares.map(function (middleware) {
        return middleware(middlewareAPI);
      });
      // 执行 compose(chain)
      // 传入 store.dispatch
      _dispatch = compose.apply(void 0, chain)(store.dispatch);
      return _objectSpread2(_objectSpread2({}, store), {}, {
        dispatch: _dispatch
      });
    };
  };
}
// compose
function compose() {
  for (var _len = arguments.length, funcs = new Array(_len), _key = 0; _key < _len; _key++) {
    funcs[_key] = arguments[_key];
  }

  if (funcs.length === 0) {
    return function (arg) {
      return arg;
    };
  }

  if (funcs.length === 1) {
    return funcs[0];
  }

  return funcs.reduce(function (a, b) {
    return function () {
      return a(b.apply(void 0, arguments));
    };
  });
}

// compose(fn1, fn2, fn3, fn4) 
// fn1(fn2(fn3(fn4(x))))
// 最后一个中间件最先执行

简单使用 react-redux

// store 保持不变
// 改造一下 src/page/Todo.tsx

import { actionTypes } from '../redux/count'
import { connect } from 'react-redux';

function Todo(props: { onAdd: any; onSub: any; count: any; }) {
  // store 发生变化时,会自动更新 props, 重新进行渲染
  const { onAdd, onSub, count } = props
  return <>
    <button onClick={() => onAdd(1)}>+</button>
    <div>{count}</div>
    <button onClick={() => onSub(1)}>-</button>
  </>
}

// 返回 store 中的 count
const mapStateToProps = (state: { countState: { count: number; }; }, ownProps: any) => {
  console.log(ownProps) // 组件原本的 props
  return {
    count: state.countState.count
  }
}

// 处理 action
const mapDispatchToProps = (dispatch: (arg0: { type: string; payload: number; }) => void, ownProps: any) => {
  console.log(ownProps) // 组件原本的 props
  return {
    // 同样可以使用 async/await
    onAdd: async (count: number) => {
      let n = await new Promise((resolve) => {
        let timeout = setTimeout(() => {
          clearTimeout(timeout)
          resolve(count)
        }, 1000)
      })
	  // 使用 dispatch 进行提交
      dispatch({
        type: actionTypes.ADD,
        payload: n as number
      })
    },
    onSub: (count: number) => {
      dispatch({
        type: actionTypes.SUB,
        payload: count
      })
    }
  }
}

// 使用 connect 高级组件包裹 Todo
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Todo)
// app.js
// 在 app.js 界面中将 store 传入 Provider,使得组件内部得以获取到 state
// 注意 store 的导入位置,存在拆分的关系,需要在组件导入前,导入 store, 以避免出现变量 undefined 的情况
import './App.css';
import store from './redux/store'
import Todo from './page/Todo'
import { Provider } from 'react-redux'
function App() {
  return (
    // 使用 Provider 包裹组件,并传入 store
    <Provider store={store}>
      <div className="App">
        <Todo test={123}></Todo>
      </div>
    </Provider >
  );
}

export default App;

补充

// mapDispatchToProps 省略写法
const mapDispatchToProps = (dispatch: (arg0: { type: string; payload: number; }) => void, ownProps: any) => {
  return {
    onAdd: (count: number) => {
      dispatch({
        type: actionTypes.ADD,
        payload: count
      })
    },
    onSub: (count: number) => {
      dispatch({
        type: actionTypes.SUB,
        payload: count
      })
    }
  }
}
// 可省略(采用对象的形式)为
const mapDispatchToProps = {
  onAdd: (count: number) => {
    return {
      type: actionTypes.ADD,
      payload: count
    }
  },
  onSub: (count: number) => {
    return {
      type: actionTypes.SUB,
      payload: count
    }
  }
}
// 使用对象模式,将无法使用 async/await
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值