Redux

Flux演变而来,作为状态容器,提供可预测的状态管理。

跨组件,多层级组件,想要 需要共享的数据,可以使用。

能不用尽量不用。

redux 库可以脱离 React 应用使用。

安装Redux- DevTools

chrome 商店 收索 redux dev 即可安装。
安装之后还不可以使用。
创建 store 时,添加以下代码
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()

let store = createStore(
    reducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)

基本概念

state

State 是只读的。

Store对象包含所有数据。由 store 管理且由 getState() 方法获得。它表示了 Redux 应用的全部状态。

state 可以是任意的数据类型。然而你应尽可能确保 state 可以被序列化,而且不要把什么数据都放进去,导致无法轻松地把 state 转换成 JSON。

Action

Action是一个用来描述修改 state 的操作。通过 dispatch 告诉 store , state 如何修改。

action只是一个传递信息的,他不能改变store。store 只能被 reducer改变。

action必须有一个type属性,表明即将执行的action的类型,type通常被定义为一个字符常量。action对象中的其他属性是携带的信息。

const action = {
  type: 'ADD_TODO',
  payload: 1
};

上面代码中,Action 的名称是ADD_TODO,它携带的信息是number: 1。

Action Creator

action creator是创建action的函数的工厂,简单地返回action,它能根据传入的参数设置action的属性值。

const ADD_TODO = '添加 TODO';

function addTodo(number) {
  return {
    type: ADD_TODO,
    number
  }
}
const action = addTodo(3);
Reducer

当store 收到 Action 以后,必须给出一个新的 State。这种 State 的计算过程就叫做 Reducer。

它要做的仅仅是 —— 负责初始 state,当 store 收到 Action 以后,根据 state 和 action 返回新的 state。这种 State 的计算过程就叫做 Reducer。

可以理解为一个专门处理state的工厂 给他一个旧数据它会根据不同action.type返回新的数据 也就是:旧state + action = 新state,每个项目有且可以有多个reducer。

Reducer 是一个纯函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。

纯函数:

  1. 传入相同的参数会返回相同的结果。
  2. 执行纯函数不会造成副作用。(这里的副作用指的是函数改变了作用域之外的状态值,比如函数外部定义了变量a,在函数内部改变了变量a的值。)
可用 immutable.js 来保证数据的不可变性
/*
 根据传入的 action 的 type 不同,返回一个新的 state 数据
*/
// 先初始化 state
const initCounter = 0;
const reducer = function (state = initCounter, action) {
       const { number } = action;
       let data = {...state};
    switch (action.type) {
        case 'ADD_NUMBER':
          return data + number
        default: 
          return state
    }
};

reducer 函数 不是修改 state,而是 深度拷贝 state 得到新对象,来操作这个新对象并当作 新的 state 返回。

使用 ES7 的{ ...state }Object.assign({},state,{})实现浅拷贝满足使用

JSON.parse(JSON.stringify(state)) 对象的深度拷贝

default 情况下返回旧的 state遇到未知的 action 时,一定要返回旧的 state

combineReducers

合并 多个 reducer 的方法。

一个 reducer 处理 同一个 state,不可避免的 导致 state 过于庞大 和 reducer 方法臃肿。

把 reducer 函数 拆分成多个单独的函数,拆分后的每个函数负责独立管理 state的一部分。

combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore方法。

let data = {
    '1': {
        arr:[1,2,3],
        number:1,
    },
    '2':{
        msg: 'hello'
    }
}
let reducerOne = (state=data[1],action) =>{
    let data = JSON.parse(JSON.stringify(state));
    switch (action.type) {
        case 'ADD_NUMBER':
            data.number += action.number;
            return data;
        case 'ADD_ARR':
            data.arr.push(action.number);
            return data;
        default:
            return data;
    }
}

let reducerTwo = (state=data[2],action) =>{
    let data = JSON.parse(JSON.stringify(state));
    switch (action.type) {
        case 'ADD_STRING':
            data.msg += action.msg;
            return data;
        default:
            return data;
    }
}
let reducer = combineReducers({
    reducerOne,reducerTwo
});
/*
当你触发 action 后,combineReducers 返回的 reducer 会负责调用reducerOne,reducerTwo
*/
const store = createStore(reducer);

console.log(store.getState())
/*
{reducerTwo: {…}, reducerOne: {…}}
    reducerOne: {arr: Array(3), number: 1}
    reducerTwo: {msg: "hello"}
    __proto__: Object
*/
dispatch

dispatch 是一个接收 action 的函数,往 store 分发一个或多个 action,要么不分发任何 action。

store.dispatch({
  type: 'ADD_TODO',
  number: 5
});
// 或者
store.dispatch(addTodo(5));
createStore

创建store 的方法。

createStore(reducer, preloadedState?, enhancer?)

  • 上面的 reducer 函数,必须有.
  • preloadedState 可选参数,初始时的 state。 如果你使用 combineReducers 创建 reducer,它必须是一个普通对象,与传入的 keys 保持同样的结构。否则,你可以自由传入任何 reducer 可理解的内容。
  • enhancer 可选参数,store的增强器,顾名思义,就是增强store的功能。比如 使用第三方插件,react-thunk
let reducerOne = (state={},action) =>{
    let data = JSON.parse(JSON.stringify(state));
    switch (action.type) {
        case 'ADD_NUMBER':
            data.number += action.number;
            return data;
        default:
            return data;
    }
}
let reducerTwo = (state={},action) =>{
    let data = JSON.parse(JSON.stringify(state));
    switch (action.type) {
        case 'ADD_STRING':
            data.msg += action.msg;
        default:
            return data;
    }
}
/*
combineReducers 创建的reducer,在createStore 中初始化 state 时,
属性值 是 变量时,必须重新起属性名,属性值和属性名不能相同。
     像这样 是不能正常初始化 state

    let reducer = combineReducers({
        reducerOne,reducerTwo
    });
    let data = {
        'reducerOne':{
            msg: 'hello'
        },
        'reducerTwo': {
            arr:[1,2,3],
            number:1,
        },
    }
*/
let reducer = combineReducers({
   'one':reducerOne,
   'two':reducerTwo
});
let data = {
    'one':{
        msg: 'hello'
    },
    'two': {
        arr:[1,2,3],
        number:1,
    },
}
const store = createStore(reducer,data);

store

Store 就是保存数据的地方,一个仓库。整个应用只能有一个 Store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合而不是创建多个 store。

Store将actions和reducers结合起来,store的功能是:

  1. 维持应用的 state
  2. 通过getState()拿到state。
  3. 通过dispatch(action)更新state。
  4. 通过subscribe(listener)注册监听器。listener是一个函数,当发送action的时候会执行listener。

    每次state变更时,都会触发其订阅的事件 listener。
  5. 执行subscribe(listener)会返回 一个函数,调用这个函数能够注销监听器。

通过 创建一个 store,传入 reducer,每当我们在 store 上 dispatch 一个 action,reducer 会根据 action的 type ,做修改 state 的操作,store 内的数据就会相应地发生变化。

// 创建 一个 store,传入 reducer
const store = createStore(reducer);

// 组件中 注册 subscribe(listener) 绑定 用于 setState 的函数
class App extends React.Component{
    constructor(props) {
        super(props);
        this.unsubscribe = store.subscribe(this.changeStore);
    }  
    state = {
        num: store.getState().reducerOne.number,
    }
    changeStore = ()=>{
        this.setState({num:store.getState().reducerOne.number});
    }

    componentWillUnmount() {
        this.unsubscribe();  //注销监听器
    }
    
    handClick = () =>{
        store.dispatch({
            type:'ADD_NUMBER',
            number: 1,
        })
    }
    render() {
        return <div>
                <button onClick={this.handClick}>点击</button>
                <p>{this.state.num}</p>
            </div>
    }
}
ReactDOM.render(<App />,document.getElementById('app'))

Redux-thunk --- 中间件(middleware)

中间件就是一个函数,对store.dispatch方法进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。

也就是 改变action -> reducer 的过程。变为 action -> middlewares(中间件) -> reducer 。使用它改变数据流,实现异步 action .

Action 发出以后,Reducer 立即算出 State,这叫做同步。Action 发出以后,过一段时间再执行 Reducer,这就是异步。

redux-thunk中间件改造了redux的dispatch方法允许我们用store.dispatch(fn), fn可以是一个函数。而且此函数可以接受两个参数:dispatchgetState做为参数。

也就是 store.dispatch() store.getState() 的封装
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'

// reducer
let data = {};
let reduce = (state=data,action) =>{
    let data = JSON.parse(JSON.stringify(state));
    switch (action.type) {
        case 'GET_LIST':
            data.list = action.list;
            return data;
        default:
            return data;
    }
};
    
// 解决redux 和 redux devtools 的冲突问题
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
// 加载 redux-thunk
const enhancer = composeEnhancers(applyMiddleware(thunk));

const store = createStore(reducer,enhancer);


//action
function incrementIfOdd() {
    return async (dispatch, getState) => {
        let list = await fetch(
            'http://rap2.taobao.org:38080/app/mock/254619/redux-thunk'
        ).then(res => res.json());

        dispatch({
            type: 'GET_LIST',
            list
        });
        console.log(getState()); 
        /*
            {
                arr: (3) [1, 2, 3]
                msg: "hello"
            }
        */
    }
}

React-Redux

react-redux是Redux 的作者封装了一个 React 专用的库 React-Redux

Redux 本身和React没有关系,只是数据处理中心,是React-Redux让他们联系在一起。

这个库是可以选用的。实际项目中,你应该权衡一下,是直接使用 Redux,还是使用 React-Redux。后者虽然提供了便利,但是需要掌握额外的 API,并且要遵守它的组件拆分规范。

React-Redux 将所有组件分成两大类:UI 组件和容器组件

  1. 前者会处理逻辑
  2. 后者只负责显示和交互,内部不处理逻辑,状态完全由外部掌控

两个核心概念

Provider 的组件

将顶层组件包裹在Provider组件之中,这样的话,所有组件就都可以在react-redux的控制之下了,但是store必须作为参数放到Provider组件中去

<Provider store = {store}>
    <App />
<Provider>
通过用 Provider 组件包装整个应用,App 组件的所有子组件都可以访问 Redux store。

connect(stateProps, dispatchProps)(ComponentName)

映射器,在需要用到 state 或 dispatch(action) 的组件中使用 connect 函数( 即高阶组件) 进行包装。

可以看到connect(stateProps, dispatchProps)(ComponentName) 调用了两次。

其实connect 是一个高阶函数,它简单说就是当你调用它时会返回一个函数。然后调用返回的函数传入一个组件时,它会返回一个新(包装的)组件

stateProps(state, ownProps?)

它是个自定义函数,从Redux 状态树中提取需要的state 作为props传递给当前的组件。

所以他的作用就是其实也就是当 redux 的state 改变,props 改变重新 渲染依赖组件。

stateProps 结果一定要返回一个object 。

  • 第一个参数 state , 相当于 store.getState() 的封装,但是 你可以只获取 state 中的任意一个或多个数据。
  • 第二个可选 参数ownProps ,父组件 传递给 组件 的 props。
dispatchProps(dispatch, ownProps?)

把 组件内 分发 action 的方法 当作 props 。

  • dispatch参数: 把 store.dispatch() 方法封装,直接调用 dispatch。
  • ownProps,父组件 传递给 组件 的 props。
function Bt(props) {
    return (
        <div>
            <button onClick={props.handClick}>点击</button>
            <p>{props.number}</p>
        </div>
    )
}
let stateProps = state => {
    return {
        number: state.number
    }
}
let dispatchProps = dispatch =>{
    return {
        handClick: e => dispatch({
            type:'ADD_NUMBER',
            number: 1,
        })
    }
}

// connect()() 返回的是一个组件
let Button = connect(stateProps,dispatchProps)(Bt);

class App extends React.Component{
    constructor(props) {
        super(props);
    }

    render() {
        return  (
            <Provider store = {store}>
                <Button></Button>
            </Provider>
    )}
}
ReactDOM.render(<App/>,document.getElementById('app'))
Redux系列之分析中间件原理(附经验分享)
Redux 中文文档
Redux 入门到高级教程
[译] 2019 React Redux 完全指南
react-redux一点就透,我这么笨都懂了!
React Redux
一篇文章总结redux、react-redux、redux-saga

Sanbai
30 声望1 粉丝

正在学习前端的小白