👝七、进阶组件的拆分
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 函数时,我们可以把它称之为是一个无状态组件。
那无状态组件怎么定义呢??
我们可以定义一个函数,这个函数接收一个参数,props 。 TodoListUI.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 中间件的原理。
所谓中间件,肯定就是说是谁和谁的中间。我们先来看一张图:
Redux 中间件的这个中间,指的是 action 和 store 之间。
之前我们说过,在 redux 中, action 只能是一个对象,就因为它是对象,因此直接把它派发给 store 。现在,当我们使用了 redux-thunk 之后, action 就可以是函数了。那为什么可以是函数呢?
看上面的图中不难发现, action 通过 dispatch 的方法,将数据递交给了 store 。且 action 和 store 之间,是一个 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-thunk , redux-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)项目目录
下面先来看项目目录。具体如下图:
(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')); 复制代码
Provider 是 react 提供的第一个核心 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 表示的是连接,那么是谁和谁做连接呢? TodoList 和 store 做连接。它们俩做连接需要一个映射关系,这个映射关系就在 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 。这样,就形成了一个数据传递的闭环。
最后,我们来看一下浏览器显示的效果:
相比于使用中间件来说, React-Redux 的使用更加地直观和简洁。在实际项目中,不管是 redux 中间件,还是 react-redux ,都值得拿来做状态管理。
那么要注意的是,redux 中间件和 react-redux 之间,各自在使用过程中不同的点,区分好即可。至于在项目中使用哪一种类型,就依据当下的项目场景去决定就好啦!
🚦十一、结束语
在上面的文章中,我们讲解了 Redux 设计和使用的三项原则,同时,也讲解了 Redux 中的一些核心 API 。除此之外呢,我们还学习了 redux 的中间件, redux-thunk 和 redux-saga 。同时,还学习了另外一个做状态管理的内容, react-redux 。
到这里,关于 redux 的内容就介绍完毕啦!不知道大家是否对 redux 又有了新的了解呢?


