React06:Redux

redux

  • Redux 是一个独立的 JavaScript 状态管理库
  • https://www.redux.org.cn/

安装 Redux

npm i redux
yarn add redux

核心概念

理解 Redux 核心几个概念与它们之间的关系

  • state 状态
  • reducer 纯函数
  • store 仓库
    • dispatch 向reducer发起action
    • getState 获取state
    • subscribe 监听state
  • action 动作

流程

  1. 首先通过createStore 创建仓库
  2. 基于render纯函reducer数初始化state
  3. 修改时通过store提供的dispatch方法发起一个action
  4. action被传入纯函数reducer中对state进行修改

Store 对象

为了对 state,Reducer,action 进行统一管理和维护,我们需要创建一个 Store 对象

import { createStore } from "redux";


function reducer(state = {}, action) {
    return state;
}

const store = createStore(reducer);
console.dir(store);

dispatch

调用 dispatch 方法,可以向 store 发起 action,

store 会调用 reducer,将 action 传递给 reducer

是一个同步方法

getState

获取 store 中存储的 state

subscribe:

用于监听 state 变化,或取消监听,用于获取最新的state

import { createStore } from "redux";


function reducer(state = {
    cnt: 1
}, action) {
    console.log(action);
    switch (action.type) {
        case "add":
            state.cnt++;
            return state;   // 把修改后的新的state返回
        default:
            break;
    }
    return state;
}

const store = createStore(reducer);
// console.dir(store);
store.subscribe(() => {
    console.log("state修改了", store.getState());
});
setInterval(() => {
    store.dispatch({
        type: "add"
    });
}, 1000);


state 对象

通常我们会把应用中的数据存储到一个对象树(Object Tree) 中进行统一管理,我们把这个对象树称为:state

state 是只读的

这里需要注意的是,为了保证数据状态的可维护和测试,不推荐直接修改 state 中的原数据

通过纯函数修改 state

什么是纯函数?

reducer 纯函数

  1. 相同的输入永远返回相同的输出
  2. 不修改函数的输入值
  3. 不依赖外部环境状态
  4. 无任何副作用

使用纯函数的好处

  1. 便于测试
  2. 有利重构
import { createStore } from "redux";


function reducer(state = {
    cnt: 1
}, action) {
    console.log(action);
    switch (action.type) {
        case "ADD":
            state.cnt++;
            return state;   // 把修改后的新的state返回
        case "MINUS":
            state.cnt--;
            return state;
    }
    return state;
}

const store = createStore(reducer);
// console.dir(store);

window.onload = function () {
    let addBtn = document.createElement("button");
    addBtn.innerHTML = "++";
    addBtn.onclick = () => {
        store.dispatch({
            type: "ADD"
        });
    };
    document.body.appendChild(addBtn);
    let p = document.createElement("p");
    render();
    document.body.appendChild(p);
    let minusBtn = document.createElement("button");
    minusBtn.innerHTML = "--";
    minusBtn.onclick = () => {
        store.dispatch({
            type: "MINUS"
        });
    };
    document.body.appendChild(minusBtn);
    store.subscribe(render);
    
    function render() {
        console.log("state修改了", store.getState());
        p.innerHTML = store.getState().cnt;
    }

};

action 对象

action 修改动作

  • action 本质就是一个对象,
  • 该对象必须有 type 属性,
  • type 属性中,描述的是我们要对 state 做出哪种修改,
  • reducer中会接收到 action ,然后完成对state的修改

我们对 state 的修改是通过 reducer 纯函数来进行的,同时通过传入的 action 来执行具体的操作

  • type 属性 : 表示要进行操作的动作类型,增删改查……
  • payload属性 : 操作 state 的同时传入的数据

但是这里需要注意的是,我们不直接去调用 Reducer 函数,而是通过 Store 对象提供的 dispatch 方法来调用

react-redux

react项目中的 redux 绑定库

  • <Provider store>
  • connect() – 高阶函数:传入数据,返回一个函数
  • useSelector 获取 state
  • useDispatch 获取 dispatch
  • useStore 获取 store

<Provider store=>

<Provider store> 使组件层级中的 connect() 方法都能够获得 Redux store

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from "react-redux";
import store from "./store/index";
import App from './app';
ReactDOM.render(
  <Provider store={store}>
    <App />,
  </Provider>,
  document.getElementById('root')
);

connect

let cb = connect((state) => {...state})
  • 返回值state必须是个对象
  • 通过connect可以让组件获取到state和dispatch
import React, { Component } from 'react';
import "./index.css";
import AddMessage from './addMessage';
import MessageList from './messageList';
import { connect } from 'react-redux';
function App(props) {
    console.log(props);
    let { data } = props;
    return <section className="wrap">
        <h2 className="title">留言板</h2>
        <AddMessage />
        {data.length > 0 ? <MessageList
            data={data}
        /> : ""}
    </section>;
}

App = connect(state => state)(App);

export default App;


useSelector

用于获取state

useSelector(state => 我们需要的state相关数据
import React from 'react';
import "./index.css";
import AddMessage from './addMessage';
import MessageList from './messageList';
import { useSelector } from 'react-redux';
function App() {
    let data = useSelector(state => state);
    return <section className="wrap">
        <h2 className="title">留言板</h2>
        <AddMessage />
        {data.length > 0 ? <MessageList /> : ""}
    </section>;
}
export default App;
import React, { Component } from 'react';
import "./index.css";
import AddMessage from './addMessage';
import MessageList from './messageList';
import { useSelector, useDispatch, useStore } from 'react-redux';

function App() {
    let data = useSelector(state => state);
    let dispatch = useDispatch();
    let store = useStore();
    console.log(data, dispatch, store);
    return <section className="wrap">
        <h2 className="title">留言板</h2>
        <AddMessage />
        {data.length > 0 ? <MessageList
            data={data}
        /> : ""}
    </section>;
}

export default App;

在这里插入图片描述

useDispatch 获取 dispatch

useStore 获取 store

实例

留言store
设置store创建仓库
设置action 方法

import { createStore } from "redux";
function reducer(state = [{
    id: 0,
    name: "昵称",
    message: "留言内容留言内容留言内容留言内容留言内容1"
}, {
    id: 1,
    name: "昵称22",
    message: "留言内容容留言内容留言内容留言内容留言内容留言内容33"
}], action) {
    switch (action.type) {
        case "ADD_MESSAGE":
            return [{
                id: Date.now(),
                name: action.name,
                message: action.message
            }, ...state];
        case "REMOVE_MESSAGE":
            return state.filter(item => item.id !== action.id);
    }
    return state; //初始化
}
const store = createStore(reducer);
export default store;

组件获取store中的state,渲染数据

import React from "react";
import Message from "./message";
import { useSelector } from "react-redux";
export default function MessageList() {
    let data = useSelector(state => state);
    return <ul className="messageList">
        {
            data.map(item => <Message key={item.id} data={item} />)
        }
    </ul>;
}

组件dispatch提交方法

import React from "react";
import { useDispatch } from "react-redux";
export default function Message(props) {
    let { data } = props;
    let { id, name, message } = data;
    let dispatch = useDispatch();
    return <li>
        <h3>{name}</h3>
        <p>{message}</p>
        <a onClick={() => {
            dispatch({
                type: "REMOVE_MESSAGE",
                id
            });
        }}>删除</a>
    </li>;
}

redux 三大原则

  • 单一数据源: 整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中
  • State 是只读的: 唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象
  • 使用纯函数来执行修改

中间件

更新的过程中,去做一些其他的事情,
dispatch —> reducer 更新state
dispatch --> 中间件 --> reducer

异步中间件redux-thunk

因为dispatch是一个同步方法,想要一步获取数据的时候就需要异步操作

普通的异步操作

function List() {
    let { type = "all" } = useParams();
    let { loading, data } = useSelector(state => state);
    let dispatch = useDispatch();
    useEffect(() => {
        dispatch({
            type: "LOADING"
        });
        axios.get(`https://cnodejs.org/api/v1/topics?page=1&tab=${type}&limit=10`)
            .then(res => {
                dispatch({
                    type: "LOAD",
                    data: res.data.data
                });
            }, () => {
                dispatch({
                    type: "LOAD",
                    data: []
                });
            });
    }, [type]);
    console.log(data);
    return (<Fragment>
        {loading ?
            "数据获取中……"
            :
            (
                data.length > 0 ? (
                    <ul className="list">
                        {data.map(item => (<li key={item.id}>{item.title}</li>))}
                    </ul>
                ) : "暂无数据"
            )}
    </Fragment>
    );
}

redux-thunk

  • 参数是对象,直接调用 reducer 修改我们的 state
  • 参数是函数,调用该函数,并且把 dispatch 和 getState 传递我们的函数,可以在函数中,进行异步操作

在这里插入图片描述
启用thunk

import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";

function reducer(state = {
    data: [],
    loading: false
}, action) {
    switch (action.type) {
        case "LOADING": // 正在获取数据
            return {
                data: [],
                loading: true
            };
        case "LOAD"://数据获取完成有数据
            return {
                data: action.data,
                loading: false
            };
    }
    return state;
}
//添加一个中间件
const store = createStore(reducer, applyMiddleware(thunk));

export default store;

可以把所有的action提取出去,减少组件内部逻辑

import React, { Fragment, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import { getListData } from "./store/action";
function List() {
    let { type = "all" } = useParams();
    let { loading, data } = useSelector(state => state);
    let dispatch = useDispatch();
    useEffect(() => {
        dispatch(getListData(type));
    }, [type]);
    return (<Fragment>
        {loading ?
            "数据获取中……"
            :
            (
                data.length > 0 ? (
                    <ul className="list">
                        {data.map(item => (<li key={item.id}>{item.title}</li>))}
                    </ul>
                ) : "暂无数据"
            )}
    </Fragment>
    );
}
export default List;

抽离出来的dispatch方法

import axios from "axios";
function getListData(type) {
    return (dispatch, getState) => {
        dispatch({
            type: "LOADING"
        });
        axios.get(`https://cnodejs.org/api/v1/topics?page=1&tab=${type}&limit=10`)
            .then(res => {
                dispatch({
                    type: "LOAD",
                    data: res.data.data
                });
            }, () => {
                dispatch({
                    type: "LOAD",
                    data: []
                });
            });
    };
}
export { getListData };

hooks替代thunk异步(hooks才是未来)

    let getListData = useListData();
    useEffect(() => {
        getListData(type);
    }, [type]);

useListData

import { useDispatch } from "react-redux";
import axios from "axios";
function useListData() {
    let dispatch = useDispatch();
    return (type) => {
        dispatch({
            type: "LOADING"
        });
        axios.get(`https://cnodejs.org/api/v1/topics?page=1&tab=${type}&limit=10`)
            .then(res => {
                dispatch({
                    type: "LOAD",
                    data: res.data.data
                });
            }, () => {
                dispatch({
                    type: "LOAD",
                    data: []
                });
            });
    };
}
export { useListData };

reducer的拆分

当state数据多的时候会比较乱

function index(state = {
    list: {},
    topic: {},
    user: {}
}, action) {
    return {
        list: list(state.list, action),
        topic: topic(state.topic = {}, action),
        user: user(state.user = {}, action)
    };
}

所以可以将每个reducer抽离出来,使得逻辑清晰

function index(state = {
    list: {},
    topic: {},
    user: {}
}, action) {
    return {
        list: list(state.list, action),
        topic: topic(state.topic = {}, action),
        user: user(state.user = {}, action)
    };
}
function list(list = {}, action) {
    return list;
}
function topic(topic = {}, action) {
    return topic;
}
function user(user = {}, action) {
    return user;
}

export default index;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值