Redux从入门到进阶,看这一篇就够了!(下)

简介: 那在下面的这篇文章中,将从入门到进阶,讲解 redux 的工作流程。

👝七、进阶组件的拆分


1、UI组件和容器组件的拆分

在上面的代码中,我们已经基本完成了 TodoList 的功能。但是呢,大家有没有发现,在 TodoList.js 文件中,页面的渲染和页面的逻辑编写是放在一起的。

往往在实际开发中,我们都会直接把 UI 组件容器组件给拆分开来。其中, UI 组件专门用于负责页面的渲染,而容器组件用于负责页面的逻辑

下面我们来对其进行拆分。首先,我们现在 src 文件夹下新增一个文件,命名为 TodoListUI.js具体代码如下:

import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
class TodoListUI extends Component {
    render() {
        return (
            <div style={{ marginTop: '10px', marginLeft: '10px' }}>
                <div>
                    <Input
                        value={this.props.inputValue}
                        placeholder="todo info"
                        style={{ width: '300px', marginRight: '10px' }}
                        onChange={this.props.handleInputChange}
                    />
                    <Button type="primary" onClick={this.props.handleBtnClick}>提交</Button>
                </div>
                <List
                    style={{ marginTop: '10px', width: '300px' }}
                    bordered
                    dataSource={this.props.list}
                    renderItem={(item, index) => <List.Item onClick={() => { this.props.handleItemDelete(index) }}>{item}</List.Item>}
                />
            </div>
        )
    }
}
export default TodoListUI;
复制代码

继续,我们来改造 TodoList.js 文件的内容。具体代码如下:

import React, { Component } from 'react';
import store from './store';
import { getInputChangeAction, getAddItemAction, getDeleteItemAction } from './store/actionCreators';
import TodoListUI from './TodoListUI';
class TodoList extends Component {
    constructor(props) {
        super(props);
        this.state = store.getState()
        this.handleInputChange = this.handleInputChange.bind(this)
        this.handleStoreChange = this.handleStoreChange.bind(this)
        this.handleBtnClick = this.handleBtnClick.bind(this)
        this.handleItemDelete = this.handleItemDelete.bind(this)
        store.subscribe(this.handleStoreChange)
    }
    render() {
        return (
            <TodoListUI
                inputValue={this.state.inputValue}
                list={this.state.list}
                handleInputChange={this.handleInputChange}
                handleBtnClick={this.handleBtnClick}
                handleItemDelete={this.handleItemDelete}
            />
        )
    }
    handleInputChange(e) {
        const action = getInputChangeAction(e.target.value);
        store.dispatch(action)
    }
    handleStoreChange() {
        this.setState(store.getState())
    }
    handleBtnClick() {
        const action = getAddItemAction();
        store.dispatch(action)
    }
    handleItemDelete(index) {
        const action = getDeleteItemAction(index);
        store.dispatch(action);
    }
}
export default TodoList;
复制代码

大家可以看到,我们把页面的内容给单独抽离出来放到 TodoListUI.js 文件当中,让它只做渲染这一件事情。这样,我们就成功的把 UI 组件和逻辑组件进行拆分。


2、无状态组件

有了 UI 组件之后,我们再来看另外一种组件,无状态组件。所谓无状态组件,就是整个页面什么逻辑都没有,只有一个 render 函数时,我们可以把它称之为是一个无状态组件。

那无状态组件怎么定义呢??

我们可以定义一个函数,这个函数接收一个参数,propsTodoListUI.js 文件的具体代码如下:

import React from 'react';
import { Input, Button, List } from 'antd';
const TodoListUI = (props) => {
    return (
        <div style={{ marginTop: '10px', marginLeft: '10px' }}>
            <div>
                <Input
                    value={props.inputValue}
                    placeholder="todo info"
                    style={{ width: '300px', marginRight: '10px' }}
                    onChange={props.handleInputChange}
                />
                <Button type="primary" onClick={props.handleBtnClick}>提交</Button>
            </div>
            <List
                style={{ marginTop: '10px', width: '300px' }}
                bordered
                dataSource={props.list}
                renderItem={(item, index) => <List.Item onClick={() => { props.handleItemDelete(index) }}>{item}</List.Item>}
            />
        </div>
    )
}
export default TodoListUI;
复制代码

当一个普通函数只有 render 函数的时候,我们完全可以通过一个无状态的组件来替换掉这个普通的组件。那为什么要做这样子的替换呢?

如果我们改造为只有一个函数的时候,那么程序就只需要去运行这个函数,也只需要做这一件事情。换言之,如果我们用 class 的话,那么它的类背后是一个对象,而这个对象又有很多的生命周期函数等等,这就显得没有那么纯粹了。因此,我们定义无状态组件这样的方式,来让组件更加地纯正。


🎩八、Redux发起异步请求


1、Redux中发送异步请求数据

往往在实际的项目中,我们总是需要去和后端请求接口数据并发送 AJAX 请求。那想要在 react 中请求到后端接口数据,该怎么处理呢?

首先我们在 TodoList.js 下面,来请求数据。具体代码如下:

import { getInputChangeAction, getAddItemAction, getDeleteItemAction, initListAction } from './store/actionCreators';
class TodoList extends Component {
  componentDidMount() {
        axios.get('./list.json').then((res) => {
            const data = res.data;
            const action = initListAction(data);
            store.dispatch(action);
        })
    }
}
复制代码

接着,修改 actionTypes.js 代码。具体如下:

export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_TODO_ITEM = 'add_todo_item';
export const DELETE_TODO_ITEM = 'delete_todo_item';
export const INIT_LIST_ACTION = 'init_list_action';
复制代码

继续,我们在  actionCreators.js 中对封装 action具体代码如下:

import { CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM, INIT_LIST_ACTION } from "./actionTypes";
export const getInputChangeAction = (value) => ({
  type: CHANGE_INPUT_VALUE,
  value: value
});
export const getAddItemAction = (value) => ({
  type: ADD_TODO_ITEM
});
export const getDeleteItemAction = (index) => ({
  type: DELETE_TODO_ITEM,
  index: index
});
export const initListAction = (data) => ({
  type: INIT_LIST_ACTION,
  data: data
})
复制代码

最后,修改 reducer.js 代码。具体代码如下:

import {
  CHANGE_INPUT_VALUE,
  ADD_TODO_ITEM,
  DELETE_TODO_ITEM,
  INIT_LIST_ACTION
} from './actionTypes';
const defaultStore = {
  inputValue: '123',
  list: [1, 2, 3]
};
// reducer 可以接收state,但是绝不能修改state
const reducer = (state = defaultStore, action) => {
  if (action.type === CHANGE_INPUT_VALUE) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.inputValue = action.value;
    return newState;
  }
  if (action.type === INIT_LIST_ACTION) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list = action.data;
    return newState;
  }
  if (action.type === ADD_TODO_ITEM) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.push(newState.inputValue);
    newState.inputValue = '';
    console.log(newState);
    return newState;
  }
  if (action.type === DELETE_TODO_ITEM) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.splice(action.index, 1);
    return newState;
  }
  return state;
}
export default reducer;
复制代码

由此,我们就实现了通过 axios 的方式来发布 AJAX 请求,请让其获取到数据。


2、Redux-thunk中间件


(1)解决什么问题

在上面的例子中,我们成功地对接口的数据发起了请求。上面这种情况是属于比较简单的例子,但是往往在实际场景中我们遇到的,都是比较复杂的例子。

因此,我们希望的是,当遇到异步请求或者是有着非常复杂逻辑的时候,把它移出到其他文件下进行管理。

那这个时候就需要用到 Redux-thunk 中间件来进行问题解决。接下来我们来看下 Redux-thunk 中间件如何使用?


(2)如何使用

第一步: 安装 redux-thunk具体命令如下:

npm i redux-thunk -D
复制代码

第二步: 引入 redux-thunk 。往往我们在实际调试中,都会受用 redux-devtools 去对项目的 store 进行调试。但如果我们既要引入 redux-devtools ,又要引入 redux-thunk 中间件,该怎么处理呢?在 store|index.js 文件中进行处理。具体代码如下:

// compose函数来自于redux中
import { createStore, applyMiddleware, compose } from "redux";
import reducer from './reducer';
import thunk from 'redux-thunk';
const composeEnhancers = 
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?   
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(
  applyMiddleware(thunk)
);
const store = createStore(
  reducer,
  enhancer
);
export default store;
复制代码

通过这种形式的编码,使得我们的 store既支持windows 下的 devtools ,也就是可以去调试 store又可以成功的引入 redux-thunk

第三步: 将异步逻辑进行抽离。先来修改 TodoList.js 的代码。具体如下:

import { getTodoList, getInputChangeAction, getAddItemAction, getDeleteItemAction } from './store/actionCreators';
class TodoList extends Component {
  componentDidMount() {
        // 这里的action是一个函数
        const action = getTodoList();
        // 只有用了thunk,action才能是用函数的形式去进行传递
        store.dispatch(action);
    }
}
复制代码

接着,修改 actionCreators.js 的代码。具体代码如下:

// getTodoList 是一个函数
// 以这种形式生成的函数,可以直接接收dispatch方法
export const getTodoList = () => {
  return (dispatch) => {
    axios.get('./list.json').then((res) => {
      const data = res.data;
      // 这里的 action 是一个对象
      const action = initListAction(data);
      dispatch(action);
    })
  }
}
复制代码

下面,我们来解释下上面这两段代码,具体如下:

配置好 redux-thunk 的环境之后,它使得我们可以在 action 里面,写异步的代码了!为什么这么说呢?

  • 以前我们在创建 action 时,只能是一个JS对象,而现在,当使用了 redux-thunk 之后,即使 getTodoList() 返回的不是一个对象而是一个函数,也可以通过 store.dispatch() 的方式,把函数发送给到 store 了。
  • 那为什么能够把函数给发送出去呢?就是因为用了 redux-thunk

继续,我们要谈论具体的实现步骤👇:

  • 首先让 TodoList.js 中的 store ,去执行 action 函数。而这个 action 函数,来自于 actionCreators.js 中的 getTodoList()
  • 对于 getTodoList() 来说,它要做的事情是去请求json的数据获取json的数据
  • 而获取好了数据之后,接下来,要改变 store 里面的数据,那么要先去创建一个 action ,这个 action 用来提供给 store.dispatch() 进行调用。但是呢, store.dispatch() 要怎么去获取呢?我们所返回的那个函数中,就会自动地接收到 store.dispatch() 方法。所以,只要通过 dispatch(action) ,将 action 给派发出去就可以了。
  • 也就是说, redux-thunk 使得我们去创建 action 或者支持 action 时,是一个函数的形式。


(3)为什么要使用 redux-thunk ?

看完上面的解释之后,相信大家也就知道 redux-thunk 的奇妙之处了。那为什么要使用 redux-thunk 呢?👇

如果把异步函数放在组件的生命周期中来使用的话,那么这个组件的逻辑就会变得越来越复杂,组件的内容也会变得越来越多。因此,我们通常就会把这种复杂的异步逻辑给拆分出去进行单独管理。那么现在,我们就借助 redux-thunk 中间件,把异步逻辑给拆分到 actionCreators.js 去进行单独管理。由此,使得代码更加规范和统一。


(4)什么是 Redux-thunk 中间件?

在有了上面内容的铺垫之后,接下来,我们返回到中间件的源头,来谈谈 Redux-thunk 中间件的原理。

所谓中间件,肯定就是说是谁和谁的中间。我们先来看一张图:

4.png

Redux 中间件的这个中间,指的是 actionstore 之间。

之前我们说过,在 redux 中, action 只能是一个对象,就因为它是对象,因此直接把它派发给 store 。现在,当我们使用了 redux-thunk 之后, action 就可以是函数了。那为什么可以是函数呢?

看上面的图中不难发现, action 通过 dispatch 的方法,将数据递交给了 store 。且 actionstore 之间,是一个 dispatch 方法,那我们说的中间件 middleware ,实际上就是对 dispatch 方法的封装和升级

对于最原始的 dispatch 方法来说,它会接收到一个 JS 对象并将其传递给 store

但如果我们传递的是一个 函数 的话,那么这个 dispatch 就升级了。 dispatch 不会直接把函数传递给 store ,它会通过 redux-thunk 中间件的方式,先执行对应的函数,等执行到需要调用 store 的时候,再去调用 store


💼九、Redux的其他中间件


1、Redux-logger

redux 的中间件非常的多,比如 redux-logger 可以记录 action 每一次派发的日志。那它怎么记录呢?

它在每一次调用 action 的时候,会通过 dispatch 方法把 action 传递给 store ,之后呢,我们可以对 dispatch 做一个升级,让 dispatch 不仅把 action 传递给 store ,而且在每一次传递之前,我们还通过 console.log 的方式将其打印出来,这样的话,我们就写了一个 redux-logger 的中间件, 它可以在我们派发 action 的时候,把 action 打印在我们的控制台里面。


2、Redux-saga


(1)Redux-saga是什么

在现如今的项目中,用的比较火的中间件不仅有 redux-thunkredux-logger ,还有 reudx-saga 的使用范围也非常的广。

reudx-saga 也是解决 react异步问题的一个中间件,不同于 redux-thunk 的是, redux-thunk 采用的是把异步操作放到 action 里面去操作。而 redux-saga 采用的设计思想是,单独地把异步逻辑拆分出来,放到另一个文件中去进行管理。那 redux-saga 这个中间件该如何使用呢?


(2)Redux-saga如何使用

我们把上面的 TodoList 组件进行升级改造。首先是 store|index.js 文件。具体代码如下:

import { createStore, applyMiddleware, compose } from "redux";
import reducer from './reducer';
import createSagaMiddleware from 'redux-saga';
import todoSagas from './sagas';
const sagaMiddleware = createSagaMiddleware()
const composeEnhancers = 
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?   
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(sagaMiddleware(sagaMiddleware));
const store = createStore(reducer, enhancer);
sagaMiddleware.run(todoSagas);
export default store;
复制代码

在这个文件当中,主要是要把基础配置做好。那这里主要有几个要注意的点是:

  • 引入 createSagaMiddleware
  • 之后是使用 const sagaMiddleware = createSagaMiddleware() 将其进行引入;
  • 使用 apllyMiddleware 去使用这个中间件;
  • 使用完中间件之后,我们又创建了 saga.js

接下来我们在 store 文件夹下创建 saga.js具体代码如下:

import { takeEvery, put } from 'redux-saga/effects';
import { initListAction } from './actionCreators';
import { GET_INIT_LIST } from './actionTypes';
import axios from 'axios';
function getInitList() {
  try {
    const res = yield axios.get('./list.json');
    const action = initListAction(res.data);
    yield put(action);
  } catch (e) {
    console.log('list.json网络请求失败');
  }
}
function* mySaga() {
  // 通过takeEvery去捕获到每一次派发下来的action
  yield takeEvery(GET_INIT_LIST, getInitList);
}
export default mySaga;
复制代码

对于 saga.js 来说,有几个要注意的点是:

  • saga.js 里面,一定要导出一个 generator 函数,在这个函数里面,我们写了一些逻辑。逻辑是,当我们接收到的 action 类型是 GET_INIT_LIST 时,那么我们就会去执行 getInitList 这个方法。
  • getInitList() 方法是一个函数,它将会去帮我们取数据,取完数据之后,再将这个数据创建出来一个新的 action ,并将这个 action 通过 yield put(action) 的方式,派发给 store

下面我们来看 actionTypes.js 中的内容。具体代码如下:

// CHANGE_INPUT_VALUE、ADD_TODO_ITEM、DELETE_TODO_ITEM、INIT_LIST_ACTION
export const GET_INIT_LIST = 'get_init_list';
复制代码

接着,我们来到 TodoList.js具体代码如下:

import { getInputChangeAction, getAddItemAction, getDeleteItemAction, getInitList } from './store/actionCreators';
class TodoList extends Component {
    // 此处省略n多内容
    componentDidMount() {
        const action = getInitList();
        store.dispatch(action);
    }
}
export default TodoList;
复制代码

最后是 store|actionCreators.js 。具体代码如下:

import { GET_INIT_LIST, CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM, INIT_LIST_ACTION } from "./actionTypes";
// 此处省略n多内容
export const getInitList = () => ({
  type: GET_INIT_LIST
});
复制代码

TodoList.js 中,我们创建了一个 action ,并将这个 action 派发给 store


🛵十、React-Redux


1、React-Redux是什么

在学习了 react 之后,紧接着,我们学习了 redux 。那如果把它们俩结合起来, react-redux 是什么呢?

实际上,它是一个第三方模块,它使得我们在 react 中更加方便地使用 redux


2、React-Redux的使用


(1)安装React-Redux

同样地,我们以 TodoList 组件为例,来看下 react-redux 的使用。首先新创建一个 react 项目,同时安装 react-redux具体命令如下:

npm install react-redux
复制代码


(2)项目目录

下面先来看项目目录。具体如下图:

5.png


(3)核心内容

第一步,将 TodoList 组件挂载到页面上。src|index.js 文件下的内容如下:

import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './TodoList';
import { Provider } from 'react-redux';
import store from './store';
const App = (
  // 表示Provider里面所有的组件,都有能力获取到store
  <Provider store={store}>
    <TodoList />
  </Provider>
)
ReactDOM.render(App, document.getElementById('root'));
复制代码

Providerreact 提供的第一个核心 API ,它旨在表明, Provider 里面所有的组件,都有能力获取到 store


第二步,编写 src|TodoList.js 的内容。具体代码如下:

import React from 'react';
import { connect } from 'react-redux';
const TodoList = (props) => {
  const { inputValue, list, changeInputValue, handleClick, handleDelete } = props;
  return (
    <div>
      <div>
        <input value={inputValue} onChange={ changeInputValue }/>
        <button onClick={ handleClick }>提交</button>
      </div>
      <ul>
        {
          list.map((item, index) => {
            return <li onClick={handleDelete} key={index}>{ item }</li>
          })
        }
      </ul>
    </div>
  )
}
const mapStateToProps = (state) => {
  return {
    inputValue: state.inputValue,
    list: state.list
  }
}
// store, dispatch, props
const mapDispatchToProps = (dispatch) => {
  return {
    changeInputValue(e) {
      const action = {
        type: 'change_input_value',
        value: e.target.value
      };
      // console.log(action.value)
      dispatch(action);
    },
    handleClick() {
      const action = {
        type: 'add_item'
      }
      dispatch(action);
    },
    handleDelete() {
    }
  }
}
// 让我们的TodoList和store做连接
// TodoList是一个UI组件,connect把这个UI组件和前边的业务逻辑相结合,可以把前面括号的内容称为是容器组件
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
复制代码

在上面的代码中,我们要注意的是 react-redux 中的 connect

connect 表示的是连接,那么是谁和谁做连接呢TodoListstore 做连接。它们俩做连接需要一个映射关系,这个映射关系就在 mapStateToProps 里面。

mapStateToProps 中, state 指的是 store 里面的数据,那 store 里面的数据,就把它映射到 props 里面,之后我们就可以通过 this.props.xxx 的方式,去获取到 store 里面的数据。


第三步,创建 reducer 。在 src|store|reducer.js 下进行编写,具体代码如下:

const defaultState = {
  inputValue: '',
  list: []
}
export default (state = defaultState, action) => {
  if (action.type === 'change_input_value') {
    const newState = JSON.parse(JSON.stringify(state));
    newState.inputValue = action.value;
    return newState;
  }
  if (action.type === 'add_item') {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.push(newState.inputValue);
    newState.inputValue = '';
    return newState;
  }
  return state;
}
复制代码

store 中的数据给放到 reducer 当中去进行记录。


第四步,将 reducer 传给 store 。在 src|store|index.js 下进行编写,具体代码如下:

import { createStore } from 'redux';
import reducer from './reducer';
const store = createStore(reducer);
export default store;
复制代码

我们将 reducer 中存放的内容进行深拷贝,并把它传回给 store 。这样,就形成了一个数据传递的闭环。


最后,我们来看一下浏览器显示的效果:

6.png

相比于使用中间件来说, React-Redux  的使用更加地直观简洁。在实际项目中,不管是 redux 中间件,还是 react-redux ,都值得拿来做状态管理。

那么要注意的是,redux 中间件和 react-redux 之间,各自在使用过程中不同的点,区分好即可。至于在项目中使用哪一种类型,就依据当下的项目场景去决定就好啦!


🚦十一、结束语


在上面的文章中,我们讲解了 Redux 设计和使用的三项原则,同时,也讲解了 Redux 中的一些核心 API 。除此之外呢,我们还学习了 redux 的中间件, redux-thunkredux-saga 。同时,还学习了另外一个做状态管理的内容, react-redux

到这里,关于 redux 的内容就介绍完毕啦!不知道大家是否对 redux 又有了新的了解呢?


相关文章
|
3月前
|
API
Vue3进阶5个小知识点 附带源码
Vue3进阶5个小知识点 附带源码
44 0
|
8月前
|
编译器 Linux C语言
【C++】C++入门详解 I【C++入门 这一篇文章就够了】(上)
【C++】C++入门详解 I【C++入门 这一篇文章就够了】
48 1
|
8月前
|
编译器 Linux C语言
【C++】C++入门详解 I【C++入门 这一篇文章就够了】(下)
【C++】C++入门详解 I【C++入门 这一篇文章就够了】
51 0
|
8月前
|
存储 安全 编译器
【C++】C++入门详解 II【深入浅出 C++入门 这一篇文章就够了】(下)
【C++】C++入门详解 II【深入浅出 C++入门 这一篇文章就够了】(上)
80 0
|
8月前
|
Java 编译器 测试技术
【C++】C++入门详解 II【深入浅出 C++入门 这一篇文章就够了】(上)
【C++】C++入门详解 II【深入浅出 C++入门 这一篇文章就够了】
46 0
|
存储 前端开发 JavaScript
新手也可以读懂的 React18 源码分析
打造全网最简单,新手也可以看懂的 React 18 源码分析系列。共同学习 React 设计思想,提升编码能力,轻松应对前端面试
220 0
新手也可以读懂的 React18 源码分析
|
JavaScript 前端开发 中间件
【react入门手册】学习react-redux,看这篇文章就够啦!
【react入门手册】学习react-redux,看这篇文章就够啦!
114 0
|
存储 缓存 JavaScript
最新Vue常见基础面试题(看这一篇就够了)(二)
最新Vue常见基础面试题(看这一篇就够了)(二)
139 0
最新Vue常见基础面试题(看这一篇就够了)(二)
|
存储 缓存 JavaScript
我学会了,react上手知识点(下篇)
本篇文章记录的是上手react并做好一个React项目的大概知识点,比如antd、transition、redux、react-redux、middleware、reducer、router、hooks等等。
201 0
我学会了,react上手知识点(下篇)
|
前端开发 JavaScript 中间件
我学会了,react上手知识点(上篇)
本篇文章记录的是上手react并做好一个React项目的大概知识点,比如jsx本质、生命周期、组件嵌套、父子通信、组件通信、插槽机制、跨组件通信、setState、react性能优化、ref、受控和非受控组件、高阶组件、React中使用样式。
131 0
我学会了,react上手知识点(上篇)