一路繁花似锦绣前程
失败的越多,成功才越有价值

导航

 

十四、Redux的使用一

1、javascript纯函数
* javascript纯函数简单总结
    - 确定的输入,一定会产生确定的输出
    - 函数在执行过程中,不能产生副作用
* React中要求我们无论是函数还是class声明一个组件,这个组件都必须像纯函数一样,保护它们的props不被修改
* 在redux中,reducer也被要求是一个纯函数
2、为什么需要redux
* react是在视图层帮助我们解决了dom的渲染过程,但是state依然是留给我们自己来管理
    - UI = render(state)
* redux就是一个帮助我们管理state的容器:redux是javascript的状态容器,提供了可预测的状态管理
3、redux的核心理念
* store
* action
    - 所有数据的变化,必须通过派发(dispatch)action来更新
    - action是一个普通的javascript对象,用来描述这次更新的type和content
* reducer
    - reducer是一个纯函数
    - reducer做的事情就是将传入的state和action结合起来生成一个新的state
4、redux的三大原则
* 单一数据源
* state是只读的
* 使用纯函数来执行修改
5、redux的基本使用
// 安装:npm i redux
const redux = require("redux")

const initialState = {
  counter: 0
}

// reducer
function reducer(state = initialState, action) {
  switch (action.type) {
    case "INCREMENT":
      return {...state, counter: state.counter + 1}
    case "DECREMENT":
      return {...state, counter: state.counter - 1}
    case "ADD_NUMBER":
      return {...state, counter: state.counter + action.num}
    case "SUB_NUMBER":
      return {...state, counter: state.counter - action.num}
    default:
      return state
  }
}

// store(创建的时候需要传入一个reducer)
const store = redux.createStore(reducer)

// 订阅store的修改
store.subscribe(() => {
  console.log("counter:", store.getState().counter)
})

// actions
const action1 = {type: "INCREMENT"}
const action2 = {type: "DECREMENT"}
const action3 = {type: "ADD_NUMBER", num: 5}
const action4 = {type: "SUB_NUMBER", num: 12}

// 派发action
store.dispatch(action1)
store.dispatch(action2)
store.dispatch(action2)
store.dispatch(action3)
store.dispatch(action4)
6、node中对ES6模块化的支持
* node v13.2.0之前,需要进行如下操作
    - 在package.json中添加属性:"type": "module"
    - 在执行命令中添加如下选项:node --experimental-modules src/index.js
* node v13.2.0之后,只需要进行如下操作
    - 在package.json中添加属性:"type": "module"
* 注意:导入文件时,需要跟上.js后缀名
7、redux的结构化分
  • index.js
import store from "./store/index.js"
import {addAction, subAction, incAction, decAction} from "./store/actionCreators.js"

store.subscribe(() => {
  console.log(store.getState())
})

store.dispatch(addAction(10))
store.dispatch(addAction(15))
store.dispatch(subAction(8))
store.dispatch(subAction(5))
store.dispatch(incAction())
store.dispatch(decAction())
  • store/index.js
import redux from "redux"
import reducer from "./reducer.js"

const store = redux.createStore(reducer)

export default store
  • store/actionCreators.js
import {INCREMENT, DECREMENT, ADD_NUMBER, SUB_NUMBER} from "./constants.js"

export const addAction = num => ({
  type: ADD_NUMBER,
  num
})

export const subAction = num => ({
  type: SUB_NUMBER,
  num
})

export const incAction = () => ({
  type: INCREMENT
})

export const decAction = () => ({
  type: DECREMENT
})
  • store/reducer.js
import {INCREMENT, DECREMENT, ADD_NUMBER, SUB_NUMBER} from "./constants.js"

const defaultState = {
  counter: 0
}

function reducer(state = defaultState, action) {
  switch (action.type) {
    case INCREMENT:
      return {...state, counter: state.counter + 1}
    case DECREMENT:
      return {...state, counter: state.counter - 1}
    case ADD_NUMBER:
      return {...state, counter: state.counter + action.num}
    case SUB_NUMBER:
      return {...state, counter: state.counter - action.num}
    default:
      return state
  }
}

export default reducer
  • store/constants.js
export const INCREMENT = "INCREMENT"
export const DECREMENT = "DECREMENT"
export const ADD_NUMBER = "ADD_NUMBER"
export const SUB_NUMBER = "SUB_NUMBER"
8、react和redux结合
import React, {PureComponent} from "react";
/**
 * 1、react中使用和node中使用的唯一区别:redux引入方式不一样
 *     - node:import redux from "redux"
 *     - react:import * as redux from "redux"
 */
import store from "@/store";
import {addAction, subAction} from "@/store/actionCreators";

class Home extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      counter: store.getState().counter
    }
  }

  componentDidMount() {
    this.unsubscribe = store.subscribe(() => {
      this.setState({
        counter: store.getState().counter
      })
    })
  }

  componentWillUnmount() {
    this.unsubscribe()
  }

  render() {
    return (<div>
      <h1>Home</h1>
      <h2>当前计数:{this.state.counter}</h2>
      <button onClick={e => this.increment()}>+1</button>
      <button onClick={e => this.addNumber(5)}>+5</button>
    </div>)
  }

  increment() {
    store.dispatch(addAction(1))
  }

  addNumber() {
    store.dispatch(addAction(5))
  }
}

class About extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      counter: store.getState().counter
    }
  }

  componentDidMount() {
    this.unsubscribe = store.subscribe(() => {
      this.setState({
        counter: store.getState().counter
      })
    })
  }

  componentWillUnmount() {
    this.unsubscribe()
  }

  render() {
    return (<div>
      <h1>About</h1>
      <h2>当前计数:{this.state.counter}</h2>
      <button onClick={e => this.decrement()}>-1</button>
      <button onClick={e => this.subNumber(5)}>-5</button>
    </div>)
  }

  decrement() {
    store.dispatch(subAction(1))
  }

  subNumber() {
    store.dispatch(subAction(5))
  }
}

export default class App extends PureComponent {
  render() {
    return (<>
      <Home/>
      <hr/>
      <About/>
    </>)
  }
}

十五、Redux的使用二

1、redux融入react代码
  • src/layout/App.js
import React, {PureComponent} from "react";
import {addAction, subAction} from "@/store/actionCreators";
import {connect} from "@/utils/connect"

const Home = connect(state => {
  return {
    counter: state.counter
  }
}, dispatch => {
  return {
    increment() {
      dispatch(addAction(1))
    },
    addNumber(num) {
      dispatch(addAction(num))
    }
  }
})(class extends PureComponent {
  render() {
    return (<div>
      <h1>Home</h1>
      <h2>当前计数:{this.props.counter}</h2>
      <button onClick={e => this.props.increment()}>+1</button>
      <button onClick={e => this.props.addNumber(5)}>+5</button>
    </div>)
  }
})

const About = connect(state => {
  return {
    counter: state.counter
  }
}, dispatch => {
  return {
    decrement() {
      dispatch(subAction(1))
    },
    subNumber(num) {
      dispatch(subAction(num))
    }
  }
})(function (props) {
  return (<div>
    <h1>About</h1>
    <h2>当前计数:{props.counter}</h2>
    <button onClick={e => props.decrement()}>-1</button>
    <button onClick={e => props.subNumber(5)}>-5</button>
  </div>)
})

export default class App extends PureComponent {
  render() {
    return (<>
      <Home/>
      <hr/>
      <About/>
    </>)
  }
}
  • src/utils/connect.js
import React, {PureComponent} from "react"
import store from "@/store";

export function connect(mapStateToProps, mapDispatchToProp) {
  return function enhanceHOC(WrappedComponent) {
    return class extends PureComponent {
      constructor(props) {
        super(props);
        this.state = {
          storeState: mapStateToProps(store.getState())
        }
      }

      componentDidMount() {
        this.unsubscribe = store.subscribe(() => {
          this.setState({
            storeState: mapStateToProps(store.getState())
          })
        })
      }

      componentWillUnmount() {
        this.unsubscribe()
      }

      render() {
        return (<WrappedComponent {...this.props}
                                  {...mapStateToProps(store.getState())}
                                  {...mapDispatchToProp(store.dispatch)}/>)
      }
    }
  }
}
2、connect-context脱离业务封装
  • src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './layout/App';
import store from "@/store";
import {StoreContext} from "@/utils/context"

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<StoreContext.Provider value={store}>
  <App/>
</StoreContext.Provider>);
  • src/utils/context.js
import React from "react"

const StoreContext = React.createContext()
export {
  StoreContext
}
  • src/utils/connect.js
import React, {PureComponent} from "react"
import {StoreContext} from "@/utils/context"

export function connect(mapStateToProps, mapDispatchToProp) {
  return function enhanceHOC(WrappedComponent) {
    class EnhanceComponent extends PureComponent {
      constructor(props, context) {
        super(props, context);
        this.state = {
          storeState: mapStateToProps(context.getState())
        }
      }

      componentDidMount() {
        this.unsubscribe = this.context.subscribe(() => {
          this.setState({
            storeState: mapStateToProps(this.context.getState())
          })
        })
      }

      componentWillUnmount() {
        this.unsubscribe()
      }

      render() {
        return (<WrappedComponent {...this.props}
                                  {...mapStateToProps(this.context.getState())}
                                  {...mapDispatchToProp(this.context.dispatch)}/>)
      }
    }

    EnhanceComponent.contextType = StoreContext
    return EnhanceComponent
  }
}
3、react-redux库的使用
  • src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './layout/App';
import store from "@/store";
// 变更处(npm i react-redux)
import {Provider} from "react-redux"

const root = ReactDOM.createRoot(document.getElementById('root'));
// 变更处
root.render(<Provider store={store}>
  <App/>
</Provider>);
  • src/layout/App.js
import React, {PureComponent} from "react";
import {addAction, subAction} from "@/store/actionCreators";
// 变更处
import {connect} from "react-redux"

const Home = connect(state => {
  return {
    counter: state.counter
  }
}, dispatch => {
  return {
    increment() {
      dispatch(addAction(1))
    },
    addNumber(num) {
      dispatch(addAction(num))
    }
  }
})(class extends PureComponent {
  render() {
    return (<div>
      <h1>Home</h1>
      <h2>当前计数:{this.props.counter}</h2>
      <button onClick={e => this.props.increment()}>+1</button>
      <button onClick={e => this.props.addNumber(5)}>+5</button>
    </div>)
  }
})

const About = connect(state => {
  return {
    counter: state.counter
  }
}, dispatch => {
  return {
    decrement() {
      dispatch(subAction(1))
    },
    subNumber(num) {
      dispatch(subAction(num))
    }
  }
})(function (props) {
  return (<div>
    <h1>About</h1>
    <h2>当前计数:{props.counter}</h2>
    <button onClick={e => props.decrement()}>-1</button>
    <button onClick={e => props.subNumber(5)}>-5</button>
  </div>)
})

export default class App extends PureComponent {
  render() {
    return (<>
      <Home/>
      <hr/>
      <About/>
    </>)
  }
}
4、组件中异步操作
import {connect} from "react-redux";
import {addAction} from "@/store/actionCreators";
import React, {PureComponent} from "react";

export default connect(state => {
  return {
    counter: state.counter
  }
}, dispatch => {
  return {
    increment() {
      dispatch(addAction(1))
    },
    addNumber(num) {
      dispatch(addAction(num))
    }
  }
})(class extends PureComponent {
  componentDidMount() {
    setTimeout(() => {
      this.props.addNumber(10)
    }, 3000)
  }

  render() {
    return (<div>
      <h1>Home</h1>
      <h2>当前计数:{this.props.counter}</h2>
      <button onClick={e => this.props.increment()}>+1</button>
      <button onClick={e => this.props.addNumber(5)}>+5</button>
    </div>)
  }
})
5、redux中异步操作
  • src/store/index.js
import * as redux from "redux"
import reducer from "./reducer.js"
// npm i redux-thunk
import thunkMiddleware from "redux-thunk"

// 应用一些中间件
const storeEnhancer = redux.applyMiddleware(thunkMiddleware)
const store = redux.createStore(reducer, storeEnhancer)

export default store
  • src/components/Home.js
import {connect} from "react-redux";
import {addAction, requestAction} from "@/store/actionCreators";
import React, {PureComponent} from "react";

export default connect(state => {
  return {
    counter: state.counter
  }
}, dispatch => {
  return {
    increment() {
      dispatch(addAction(1))
    },
    addNumber(num) {
      dispatch(addAction(num))
    },
    requestNumber() {
      dispatch(requestAction)
    }
  }
})(class extends PureComponent {
  componentDidMount() {
    this.props.requestNumber()
  }

  render() {
    return (<div>
      <h1>Home</h1>
      <h2>当前计数:{this.props.counter}</h2>
      <button onClick={e => this.props.increment()}>+1</button>
      <button onClick={e => this.props.addNumber(5)}>+5</button>
    </div>)
  }
})
  • src/store/actionCreators.js
import {INCREMENT, DECREMENT, ADD_NUMBER, SUB_NUMBER} from "./constants.js"

export const addAction = num => ({
  type: ADD_NUMBER,
  num
})

export const subAction = num => ({
  type: SUB_NUMBER,
  num
})

export const incAction = () => ({
  type: INCREMENT
})

export const decAction = () => ({
  type: DECREMENT
})

export const requestAction = (dispatch, getState) => {
  setTimeout(() => {
    console.log(getState())
    dispatch(addAction(10))
  }, 3000)
}

十六、devtools-saga-reducer拆分

1、redux-devtools
import * as redux from "redux"
import reducer from "./reducer.js"
import thunkMiddleware from "redux-thunk"

/**
 * 1、浏览器扩展redux-devtools,需要有如下配置
 *     - window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__返回composeEnhancers函数
 */
const composeEnhancers = (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ &&
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace: true})) || redux.compose
const storeEnhancer = redux.applyMiddleware(thunkMiddleware)
const store = redux.createStore(reducer, composeEnhancers(storeEnhancer))

export default store
2、generator和Promise一起来使用
function* bar() {
  console.log(111)
  const result = yield new Promise(resolve => {
    setTimeout(() => {
      resolve(222)
    }, 3000)
  })
  console.log(result)
}

const it = bar()
it.next().value.then(res => {
  it.next(res)
})
3、redux-saga
  • src/store/index.js
import * as redux from "redux"
import reducer from "./reducer.js"
import thunkMiddleware from "redux-thunk"
// npm i redux-saga
import createSagaMiddleware from "redux-saga"
import saga from "@/store/saga";

const composeEnhancers = (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ &&
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace: true})) || redux.compose
// 创建sagaMiddleware中间件
const sagaMiddleware = createSagaMiddleware()
const storeEnhancer = redux.applyMiddleware(thunkMiddleware, sagaMiddleware)
const store = redux.createStore(reducer, composeEnhancers(storeEnhancer))

sagaMiddleware.run(saga)

export default store
  • src/store/saga.js
import {all, takeLatest, takeEvery, put} from "redux-saga/effects"
import {FETCH_HOME_MULTIDATA} from "./constants.js"
import {addAction} from "./actionCreators"

function* fetchHomeMultidata(action) {
  const res = yield new Promise(resolve => {
    setTimeout(() => {
      resolve(10)
    }, 3000)
  })
  /**
   * 2、等价于
   *     - yield put(changeBannerAction(banners))
   *     - yield put(changeRecommendAction(recommends))
   */
  yield all([
    yield put(addAction(res)),
    yield put(addAction(res))
  ])
}

function* mySaga() {
  /**
   * 1、takeLatest和takeEvery区别
   *     - takeLatest:一次只能监听一个对应的action
   *     - takeEvery:每一个都会被执行
   */
  yield all([
    takeEvery(FETCH_HOME_MULTIDATA, fetchHomeMultidata),
    takeLatest(FETCH_HOME_MULTIDATA, fetchHomeMultidata)
  ])
}

export default mySaga;
  • src/store/actionCreators.js
import {ADD_NUMBER, SUB_NUMBER, FETCH_HOME_MULTIDATA} from "./constants.js"

export const addAction = num => ({
  type: ADD_NUMBER,
  num
})

export const subAction = num => ({
  type: SUB_NUMBER,
  num
})

export const requestAction = {
  type: FETCH_HOME_MULTIDATA
}
  • src/store/constants.js
export const INCREMENT = "INCREMENT"
export const DECREMENT = "DECREMENT"
export const ADD_NUMBER = "ADD_NUMBER"
export const SUB_NUMBER = "SUB_NUMBER"
export const FETCH_HOME_MULTIDATA = "FETCH_HOME_MULTIDATA"
4、中间件实现原理
import store from "./store.js"
import {addAction} from "./actionCreators.js"

// 1、基本做法
// console.log("dispatch前---dispatching action:", addAction(10))
// store.dispatch(addAction(10))
// console.log("dispatch后---new state:", store.getState())
// console.log("dispatch前---dispatching action:", addAction(15))
// store.dispatch(addAction(15))
// console.log("dispatch后---new state:", store.getState())

// 2、封装一个函数
// function dispatchAndLogging(action) {
//   console.log("dispatch前---dispatching action:", action)
//   store.dispatch(action)
//   console.log("dispatch后---new state:", store.getState())
// }
// dispatchAndLogging(addAction(10))
// dispatchAndLogging(addAction(15))

// 3、函数的基础之上进行优化:修改原有的dispatch(hack技术:monkeyingpatch)
// const next = store.dispatch
// function dispatchAndLogging(action) {
//   console.log("dispatch前---dispatching action:", action)
//   next(action)
//   console.log("dispatch后---new state:", store.getState())
// }
// store.dispatch = dispatchAndLogging
// store.dispatch(addAction(10))
// store.dispatch(addAction(15))

// 4、将之前的操作进行封装(封装patchLogging的代码)
function patchLogging(store) {
  const next = store.dispatch

  function dispatchAndLogging(action) {
    console.log("dispatch前---dispatching action:", action)
    next(action)
    console.log("dispatch后---new state:", store.getState())
  }

  // store.dispatch = dispatchAndLogging
  return dispatchAndLogging
}

// 封装patchThunk的功能
function patchThunk() {
  const next = store.dispatch

  function dispatchAndThunk(action) {
    if (typeof action === "function") {
      action(store.dispatch, store.getState)
    } else {
      next(action)
    }
  }

  return dispatchAndThunk
}

// patchLogging(store)
// patchThunk(store)
// store.dispatch(addAction(10))
// store.dispatch(addAction(15))
// function foo(dispatch, getState) {
//   store.dispatch(addAction(10))
// }
// store.dispatch(foo)

// 5、封装applyMiddleware
function applyMiddleware(...middlewares) {
  // const newMiddleware = [...middlewares]
  middlewares.forEach(middleware => {
    store.dispatch = middleware(store)
  })
}

applyMiddleware(patchLogging, patchThunk)

store.dispatch(addAction(10))
store.dispatch(addAction(15))
5、redux-reducer拆解
import {CHANGE_PAGEINFO, CHANGE_TOTAL} from "./constants.js"

const pageInfo = {
  pageNum: 1,
  pageSize: 10,
}

function pageInfoReducer(state = pageInfo, action) {
  switch (action.type) {
    case CHANGE_PAGEINFO:
      return {...state, ...action.data}
    default:
      return state
  }
}

const total = 0

function totalReducer(state = total, action) {
  switch (action.type) {
    case CHANGE_TOTAL:
      return action.data
    default:
      return state
  }
}

function reducer(state = {}, action) {
  return {
    pageInfo: pageInfoReducer(state.pageInfo, action),
    total: totalReducer(state.total, action)
  }
}

export default reducer
6、combineReducers函数
import {CHANGE_PAGEINFO, CHANGE_TOTAL} from "./constants.js"
// react中引入方式:import {combineReducers} from "redux"
import redux from "redux"

const pageInfo = {
  pageNum: 1,
  pageSize: 10,
}

function pageInfoReducer(state = pageInfo, action) {
  switch (action.type) {
    case CHANGE_PAGEINFO:
      return {...state, ...action.data}
    default:
      return state
  }
}

const total = 0

function totalReducer(state = total, action) {
  switch (action.type) {
    case CHANGE_TOTAL:
      return action.data
    default:
      return state
  }
}

// function reducer(state = {}, action) {
//   return {
//     pageInfo: pageInfoReducer(state.pageInfo, action),
//     total: totalReducer(state.total, action)
//   }
// }
// reducer应该是一个什么类型?function
const reducer = redux.combineReducers({
  pageInfo: pageInfoReducer,
  total: totalReducer
})

export default reducer
7、react中的state如何管理
* 目前我们已经主要学习了三种状态管理方式
    - 方式一:组件中自己的state管理
    - 方式二:context数据的共享状态
    - 方式三:redux管理应用状态
posted on 2023-01-05 22:56  一路繁花似锦绣前程  阅读(31)  评论(0编辑  收藏  举报