笔记来源:拉勾教育 - 大前端就业集训营
文章内容:学习过程中的笔记、感悟、和经验
React框架基础2
组件生命周期
React提供了自己的生命周期钩子函数
- 组件挂载阶段
- 数据更新阶段
- 组件卸载阶段
组件挂载阶段
挂载阶段:组件被创建然后插入到DOM中
生命周期方法
- constructor:设置组件的初始配置 - 最先触发
- render:将书写的JSX虚拟DOM渲染为真实DOM并呈现在页面上
- componentDidMount:挂载完成后触发
- 例如AJAX访问、事件监听、定时器、获取DOM元素等
// 引入组件
import {Component} from 'react'
// App组件
class App extends Component {
// constructor钩子
constructor() {
console.log('constructor执行了')
// 继承Component
super()
// 设置初始状态
this.state = {
num: 0
}
// 修改函数指向
this.clickBtn = this.clickBtn.bind(this)
}
// 按钮点击函数,这里特意没有用箭头函数,这样this指向了unnifinder,但是constructor可以修改
clickBtn() {
console.log(this)
}
// render钩子
render() {
console.log('render执行了')
return (
<div>
{/* 获取状态 */}
{this.state.num}
{/* 按钮 */}
<button onClick={this.clickBtn}>点击</button>
</div>
)
}
// 挂载完成后执行componentDidMount钩子函数
componentDidMount() {
console.log('componentDidMount执行了')
// 定时器
setInterval(() => {
// 每1秒num+1
this.setState({num: this.state.num +1})
},1000)
}
}
// 导出
export default App
组件更新阶段
更新组件:数据发生变化的时候组件需要重新渲染,例如外部传入的props和状态数据
相关生命周期钩子
- shouldComponentUpdata(nextProps,nextState):返回布尔值,决定组件是否更新
- render:和上面一样,将JSX虚拟DOM渲染为真实DOM
- componentDidUpdate:和上面类似,组件更新后调用
// 引入组件
import {Component} from 'react'
// App组件
class App extends Component {
constructor() {
super()
this.state = {
num: 0
}
this.clickBtn = this.clickBtn.bind(this)
}
clickBtn() {
this.setState({num: this.state.num + 1})
}
// render钩子
render() {
console.log('render执行了')
return (
<div>
{this.state.num}
<button onClick={this.clickBtn}>点击</button>
</div>
)
}
// shouldComponentUpdate钩子 - 更新第一个执行
shouldComponentUpdate(nextProps, nextState) {
console.log('shouldComponentUpdata执行了')
return true
}
// componentDidUpdate钩子 - 更新后执行
componentDidUpdate() {
console.log('componentDidUpdate执行了')
}
}
// 导出
export default App
shouldComponentUpdate会在组件更新之前执行,必须返回一个布尔值,决定组件是否更新,如果返回false组件就不会继续出发后续的render和componentDidUpdate钩子
- 常见使用场景:当父组件内容更新但自己内容没有更新的时候,如果不设置shouldComponentUpdate子组件也会跟着更新,可以在更新之前做一个判断,如果自己的没有数据要更新,可以直接返回false避免性能浪费
- 但是在新的React中,已经不建议使用这种方式,可以使用PureComponent代替,具体方法就是创建类组件的时候不再使用Component,而是使用PureComponent代替
组件卸载阶段
组件卸载:组件从DOM中删除
相关生命周期钩子
- componentWillUnMount:在组件卸载之前执行
例如:当我们在组件中设置了事件监听秒,那么当组件移除后可能这个时间还存在,所以我们就可以考虑在卸载之前删除这个监听,同理,定时器、AJAX也一样
AJAX请求
React每有自己的AJAX请求方法,所以需要使用第三方库,例如axios
// 引入组件
import {Component} from 'react'
// 引入axios进行AJAX请求
import axios from 'axios'
// App组件
class App extends Component {
constructor() {
// 继承Component
super()
// 设置状态
this.state = {
message: ''
}
}
// 渲染方法
render() {
return (
// 绑定状态数据,初始化时还没有数据
<p>数据请求结果为:{this.state.message}</p>
)
}
// 组件挂载后执行,使用async进行同步处理
async componentDidMount() {
// 调用接口获取数据
const {data} = await axios.get('http://edufront.lagou.com/front/ad/getAdList')
// 把数据交给状态数据
this.setState({message: data.message})
}
}
// 导出
export default App
请求转发
利用服务器之间不存在跨域问题的特性,客户端想要请求跨域数据,可以使用同源服务器发送这个请求拿到数据
React实现请求转发
-
配置package.json,添加proxy字段,将要请求的服务器地址写在后面即可,注意proxy只需要写服务器地址,具体的功能后缀该怎么写怎么写,当自己的服务器上没有这个后缀会自动寻找proxy服务器,但是只能配置一个服务器地址
// 直接把跨域的地址填进来即可 "proxy": "http://edufront.lagou.com 或者 http://localhost:3005(老师用的)"
// 调用的时候直接写路径就可以了,当程序走到这一步的时候会先尝试在同源服务器获取数据,当同源服务器获取不到就会使用上面设置的proxy服务器获取 async componentDidMount() { // 因为设置了proxy,所以这里直接写路径就可以了,要不然就要写成http://edufront.lagou.com/front/ad/getAdList const {data} = await axios.get('/front/ad/getAdList') this.setState({message: data.message}) }
-
使用第三方工具包:http-proxy-middleware安装并且新建一个setupProxy.js文件,同样的,获取数据的时候也不需要写前缀了,直接写地址
// 引入http-proxy-middleware const { createProxyMiddleware} = require('http-proxy-middleware') // 导出配置 module.exports = app => () { // 当程序发现‘/front’的时候会自动使用http://edufront.lagou.com app.use('/front', createProxyMiddleware({ // 转发地址 target: 'http://edufront.lagou.com', // 开启跨域 changeOrigin: true })) }
这里因为没有老师的服务器,所以没有实践
Mock / 模拟数据
一般情况下,前端和后端是一起进行开发的,所以可能当我们程序中想使用接口的时候还没有后端接口给我们使用,所以我们可以自己定义一些虚拟数据进行开发调试,后期接口开发完成只需要替换一下即可
步骤
- 目录public/路径名/接口名.json创建文件
- 组件中使用axios调用get方法获取接口文件
// public/api/student.json 创建虚拟mock数据,模拟一些学生的数据
[
{"id": "0", "name": "zs", "age": 19, "sex": "男"},
{"id": "1", "name": "ls", "age": 20, "sex": "女"},
{"id": "2", "name": "ww", "age": 19, "sex": "男"},
{"id": "3", "name": "zl", "age": 17, "sex": "女"},
{"id": "4", "name": "ll", "age": 22, "sex": "男"}
]
// src/App.js 组件中使用接口
import {Component} from 'react'
// 引入axios进行AJAX请求
import axios from 'axios'
// App组件
class App extends Component {
constructor() {
super()
// 初始化状态数据
this.state = {
// 学生列表
students: []
}
}
// 获取数据
getData = async () => {
// 直接访问路径即可,程序打包后public就是根目录
const {data} = await axios.get('api/student.json')
// 使用数据
this.setState({students: data})
}
render() {
return (
<div>
{/* 按钮:点击获取数据 */}
<button onClick={this.getData}>获取数据</button>
<ul>
{/* 遍历数据创建结构 */}
{this.state.students.map(item => (
<li key={item.id}>
<span>名字:{item.name}</span>
<span>年龄:{item.age}</span>
<span>性别:{item.sex}</span>
</li>
))}
</ul>
</div>
)
}
}
export default App
注意:这种方式只能虚拟get请求
Redux
工作流程
- 类似于Vue的Vuex,redux是一个数据管理框架,提供store统一数据存储仓库
- store就像是一个数据管理的中间人,让组件之间无需直接进行数据传递
创建store和reducer
- 安装redux和react-redux两个npm包
- index.js中获取redux中的createStore方法
- 使用这个方法创建一个store仓库,传入参数reducer函数
- reducer是一个函数,函数返回一个对象,对象内部就是数据
- reducer函数不一定非要写在index中,也不一定非要叫reducer,一般独立一个js文件
// src/index.js 创建store仓库import React from 'react'import ReactDOM from 'react-dom'import App from './App'// 引入构建Store的方法import {createStore} from 'redux'// 引入一个reducer函数(把reducer函数写在单独文件中是推荐的)import reducer from './redux/num'// 可以直接写在index中,但是不建议,一般写在单独文件中// function reducer() {// return {// num: 0// }// }// 创建store,参数传入reducer函数const store = createStore(reducer)// 可以尝试打印一下reducer函数函数返回的数据console.log(store.getState())ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root'))
// src/redux/num.js 单独创建一个reducer文件,便于管理,也是推荐的// 直接导出即可export default () => { // return的内容便是数据 return { num: 0 }}
获取store数据
- 在index中引入react-redux中的Provider方法
- 使用Provider作为一个组件,包裹App组件,并把上一部创建的store通过参数的方式传递给App,此时app中就存在props的store仓库数据了
- 在要使用store数据的组件中引入react-redux中的connect方法
- 然后在下面直接导出connect((store)=> ({ 要使用的数据 }),这个数据可以是静态的,也可以是store中的数据
// src/index.js react-redux中的方法,把创建的store传递给app组件import React from 'react'import ReactDOM from 'react-dom'import App from './App'// 引入构建Store的方法import {createStore} from 'redux'// 引入一个reducer函数(把reducer函数写在单独文件中是推荐的)import reducer from './redux/num'// 引入Provider方法传递storeimport {Provider} from 'react-redux'// 创建store,参数传入reducer函数const store = createStore(reducer)// 可以尝试打印一下reducer函数函数返回的数据console.log(store.getState())ReactDOM.render( <React.StrictMode> {/* 使用Provider把store传递给App,这样所有组件就都可以使用了 */} <Provider store={store}><App /></Provider> </React.StrictMode>, document.getElementById('root'))
// src/components/num.js 要使用数据的组件引入中的方法使用数据// 引入connect方法import {connect} from 'react-redux'function Num(props) { console.log(props) return ( // 组件中直接从props中拿数据 <p>{props.num}</p> )}// 由于connect书写比较复杂,把一部分抽离出来// store就是store数据库// 导出一个对象,对象内部直接交给组件的propsconst toData = (store)=>({ // 可以是store中的数据 num: store.num, // 也可以是静态数据(一般都是动态的) a:1, b:2})// 导出数据可以改造// connect最终导出一个函数// 然后函数直接运行,参数就是要使用的对象export default connect(toData)(Num)
修改store数据
- 在想要修改store数据的时候,可以触发props中的dispatch方法(store传递数据时候一起传递的),内部传入一个对象,对象内部设置type属性,属性名为要触发的reducer修改数据的方法
- 在reducer中允许接受第二个参数,第二个参数为一个字符串,在reducer函数中使用是swith语句判断这个参数,执行不同的方法赶回不同的值
// src/components/num.js 调用props中的dispatch方法,// 引入connect方法import {connect} from 'react-redux'function Num(props) { return ( <div> {/* 按钮点击修改数据,调用props中的dispatch方法(该方法是connect自动添加的,用来修改相应数据) 内部传入一个对象,对象的type属性的属性值就对应着reducer不同的操作 */} <button onClick={() => {props.dispatch({type: 'addNum'})}}>+</button> {/* 组件中直接从props中拿数据 */} <p>{props.num}</p> <button onClick={() => {props.dispatch({type: 'reduceNum'})}}>-</button> </div> )}// 由于connect书写比较复杂,把一部分抽离出来// store就是store数据库// 导出一个对象,对象内部直接交给组件的propsconst toData = (store)=>({ // 可以是store中的数据 num: store.num, // 也可以是静态数据(一般都是动态的) a:1, b:2})// 导出数据可以改造// connect最终导出一个函数// 然后函数直接运行,参数就是要使用的对象export default connect(toData)(Num)
// src/redux/num.js 给每一条指令指定响应动作// 把数据剥离出来const myData = { num: 11}// 直接导出即可// 导出的参数变为两个,第一个还是数据,只不过使用默认数据,第二个参数就是要执行的动作,其中的type属性名就是动作名export default ( data= myData, fnName) => { // 判断要是否要执行动作,根据type属性值判断 switch (fnName.type) { // num自增动作 case 'addNum': // 直接返回修改数据 return { num: data.num + 1 } case 'reduceNum': return { num: data.num - 1 } // 如果没有动作直接返回数据 default: return data }}
提取action代码为函数
利用connect的第二个参数,把dispatch进行改造
// 引入connect方法import {connect} from 'react-redux'function Num(props) { console.log(props) return ( <div> {/* 我们把action单独抽离,所以这里可以直接调用了 */} <button onClick={props.addNum}>+</button> <p>{props.num}</p> <button onClick={props.reduceNum}>-</button> </div> )}const toData = (store)=>({ num: store.num})// 把action单独抽离,直接返回一个对象const action = dispatch => ({ // 对象内部是各种方法,把之前写在button上的方法写在这里 addNum() { dispatch({type: 'addNum'}) }, reduceNum() { dispatch({type: 'reduceNum'}) }})// 利用connect的第二个参数改造dispatchexport default connect(toData, action)(Num)
action传递参数
// dispatch可以自定义参数,redurce可以接收到<div> <button onClick={() => {props.dispatch({type: 'addNum'})}}>+</button> <button onClick={() => {props.dispatch({type: 'addNum_n', payload: 5})}}>+5</button> <p>{props.num}</p> <button onClick={() => {props.dispatch({type: 'reduceNum'})}}>-</button></div>// redurce文件case 'addNum_n': // 直接返回修改数据 return { num: data.num + fnName.payload }
// 把action单独提取<div> {/* <button onClick={props.addNum}>+</button><hr /> */} <button onClick={() => { props.addNum_n(5) }}>+5</button> <p>{props.num}</p> <button onClick={props.reduceNum}>-</button></div// 这路需要注意 type的名字要和方法名相同,别问我怎么知道的const action = dispatch => ({ // 对象内部是各种方法,把之前写在button上的方法写在这里 addNum() { dispatch({type: 'addNum'}) }, addNum_n(payload) { dispatch({type: 'addNum_n', payload}) }, reduceNum() { dispatch({type: 'reduceNum'}) }})
自动生成action触发函数
借用redux中的bindActionCreators函数,接收两个参数,第一个参数是一个对象,第二个参数就是dispatch,会自动将action进行组装,只需要提供方法名和type类型就能自动完成,例如
// 引入connect方法import {connect} from 'react-redux'// 引入bindActionCreatorsfangfa自动组装actionimport {bindActionCreators} from 'redux'function Num(props) { console.log(props) return ( <div> <button onClick={props.addNum}>+</button><hr /> <button onClick={() => { props.addNum_n(5) }}>+5</button> <p>{props.num}</p> <button onClick={props.reduceNum}>-</button> </div> )}const toData = (store)=>({ num: store.num})const action = dispatch => ({ // bindActionCreators返回一个对象,我们在这里把它展开 ...bindActionCreators({ // 直接书写方法名 addNum() { // 直接return dispatch参数即可 return {type: 'addNum'} }, addNum_n(payload) { return {type: 'addNum_n', payload} }, reduceNum() { return {type: 'reduceNum'} } }, dispatch)})export default connect(toData, action)(Num)
可以单独封装bindActionCreators第一个参数
// src/redux/numAction.js// 单独封装bindActionCreators第一个参数,把几个方法全部导出export const addNum = () => ({type: 'addNum'})export const addNum_n = (payload) => ({type: 'addNum_n', payload})export const reduceNum = () => ({type: 'reduceNum'})// 或者改造成这样,下面引入就不需要重命名了export default { addNum : () => ({type: 'addNum'}), addNum_n : (payload) => ({type: 'addNum_n', payload}), reduceNum : () => ({type: 'reduceNum'})}
// src/components/num.js 引入单独封装的对象// 引入connect方法import {connect} from 'react-redux'// 引入bindActionCreatorsfangfa自动组装actionimport {bindActionCreators} from 'redux'// 引入单独封装的bindActionCreators参数,使用*as全部引入并且重命名import * as numAction from '../redux/numAction'function Num(props) { console.log(props) return ( <div> <button onClick={props.addNum}>+</button><hr /> <button onClick={() => { props.addNum_n(5) }}>+5</button> <p>{props.num}</p> <button onClick={props.reduceNum}>-</button> </div> )}const toData = (store)=>({ num: store.num})const action = dispatch => ({ // bindActionCreators返回一个对象,我们在这里把它展开 // 把单独封装的对象放到第一个参数即可 ...bindActionCreators(numAction, dispatch)})export default connect(toData, action)(Num)
action类型常量
我们在action和reducer中设置了一些字符串,我们可以吧这些字符串单独封装一个模块进行管理,使用的时候引入直接替换即可
// src/redux/Numtype.js 新建js文件创建并导出常量// 使用常亮存储字符串(但我感觉这不是脱裤子放屁吗?)export const ADDNUM = 'addNum'export const ADDNUM_N = 'addNum_n'export const REDUCENUM = 'reduceNum'// 其他文件引入并使用常量即可// 引入常亮并使用import {ADDNUM, ADDNUM_N, REDUCENUM} from './Numtype'
reducer拆分与合并
reducer负责获取store数据,然后根据指令去操作数据
- 当整个项目存在多个组件的时候,这些组件都有自己的数据要存在store中,这个时候如果还使用一个reducer.js文件管理就会比较复杂,所以Redux允许我们将所有组件要使用的数据拆分成多个Reducer.js文件进行管理
- 使用Redux的combineReducers可以将两个拆分的Reducer进行组合导出为一个对象
- 此时调用数据结构和之前会有变化,多个数据会变成store下面的属性,内部才是真正要使用的数据
Redux整体小Demo
这里我全新走一遍整个Redux
项目目录
Compements组件目录
// src/Components/Count.js 计数器组件// 引入connect获取store数据并注入到组件import {connect} from 'react-redux'// 引入bindActionCreators用于自动生成Actionimport {bindActionCreators} from 'redux'// 引入单独封装的bindActionCreators第一个参数import CountAction from '../Store/Action/Count'// 组件function Count(props) { return ( <> {/* 按钮用于点击修改数据,分别为+1、+5、-1、—5 */} {/* bindActionCreators会根据配置自动生成Action指令交给props,这里直接调用即可,为了传递参数,这里使用箭头函数 */} <button onClick={() => props.addCount(1)}>+1</button> <button onClick={() => props.addCount(5)}>+5</button> {/* 显示当前数据 */} <p>{props.count}</p> <button onClick={() => props.addCount(-1)}>-1</button> <button onClick={() => props.addCount(-5)}>-5</button> </> )}// 使用connect方法获取数据(会在connect调用的时候作为第一个参数)const myData = (store) => ({ count: store.CountReducer.count})// 调用bindActionCreators自动生成Action指令const myAction = dispatch => ({ // 注意这里要把dispatch传递进去 ...bindActionCreators(CountAction, dispatch)})// 导出组件,并调用connect函数,// 参数1 - 要获取的数据// 参数2 - Action并使用bindActionCreators进行自动生成export default connect(myData, myAction)(Count)
// src/Components/Student.js 学生列表组件// 调用connect获取数据并注入组件import {connect} from 'react-redux'// 引入bindActionCreators方法用于自动生成Action指令import {bindActionCreators} from 'redux'// 引入单独封装的bindActionCreators第一个参数import Actoin from '../Store/Action/Student'// 组件function Student(props) { return ( <> {/* 按钮,点击后向数据内添加一条,bindActionCreators生成指令后可以直接调用,使用箭头函数传参 */} <button onClick={() => props.addStudent({id: 2, name: 'ls'})}>添加</button> <ul> {/* 遍历数据,呈现学生列表 */} {props.students.map(item => { return( <li key={item.id}>{item.name}</li> ) })} </ul> </> )}// connect第一个参数 - 获取数据const myData = (store) => ({ students: store.StudentReducer})// connect第二个参数 - 自动生成Actoin指令const action = dispatch => ({ ...bindActionCreators(Actoin, dispatch)})// 导出组件,使用connect将数据和指令注入组件中export default connect(myData, action)(Student)
Store - 指令目录
// src/Store/Action/Count.js 累加器指令// 引入常量化字符串import {ADDCOUNT} from '../ActionType/Count'// 用于bindActionCreators的第一个参数const Action = { addCount(num) { return {type: ADDCOUNT, num} }}// 导出export default Action
//src/Store/Action/Student.js 学生列表指令// 引入常量化字符串import {ADDSTUDENT} from '../ActionType/Student'// 用于bindActionCreators第一个参数的指令const Action = { addStudent(newInfo) { return {type: ADDSTUDENT, newInfo} }}// 导出export default Action
Store/ActionType常量化字符串
// src/Store/ActionType/Count.js 计数器// 常量化字符串export const ADDCOUNT = 'addCount'// src/Store/ActionType/Student.js 学生列表// 常量化字符串export const ADDSTUDENT = 'addStudent'
Store/Reducer仓库数据、行为目录
// src/Store/Reducer/Count.js 累加器行为和数据// 引入常量化字符串import {ADDCOUNT} from '../ActionType/Count'// Count组件的数据const myData = { count: 0}// 设置要导出的数据和指令函数// 参数1:要导出的数据// 参数2:要触发的指令数据const CountReducer = ( data = myData, action) => { // 判断要执行的指令名称 switch (action.type) { // 加法 case ADDCOUNT: // 返回数据 return {count: data.count + action.num} // 默认导出原始数据 default: return data }}// 导出export default CountReducer
// src/Store/Reducer/Student.js 学生列表数据和行为// 引入常量化字符串import {ADDSTUDENT} from '../ActionType/Student'// 要导出的数据const myData = [ {id: 0, name: 'zs'}]// 要导出的数据和指令函数// 参数1 - 要导出的数据// 参数2 - 要触发的指令数据const StudentReducer = ( data = myData, action) => { switch (action.type) { // 添加学生数据 case ADDSTUDENT: return [...data, action.newInfo] // 默认导出原始数据 default: return data }}// 导出export default StudentReducer
// src/Store/Reducer/index.js 合并拆分书写的数据和行为// 引入combineReducers用于合并Reducerimport {combineReducers} from 'redux'// 引入两个单独书写的Reducerimport CountReducer from './Count'import StudentReducer from './Student'// 直接导出合并后的Reducerexport default combineReducers({ CountReducer, StudentReducer})
App组件
// 引入Component用于创建组件import {Component} from 'react'// 引入两个组件import Count from './Components/Count'import Student from './Components/Student'// 组件,使用两个子组件class App extends Component { render() { return ( <div> <Count /> <hr /> <Student /> </div> ) }}// 导出export default App
index.js组件
src/index.jsimport React from 'react'import ReactDOM from 'react-dom'import App from './App'// 引入createStore用于创建数据仓库storeimport {createStore} from 'redux'// 引入合并后的Reducerimport Reducer from './Store/Reducer/index'// 引入Provider将数据传递给Appimport {Provider} from 'react-redux'// 创建仓库const store = createStore(Reducer)ReactDOM.render( <React.StrictMode> {/* 使用Provider包裹App从而将数据传递给程序 */} <Provider store={store}> <App /> </Provider> </React.StrictMode>, document.getElementById('root'))
redux工作流程梳理
- 创建Store数据仓库用于保存数据并且关联reducer
- reducer内部会保存初始数据和指令函数,执行指令后会把新的数据返回给store
- 利用provider将store向后传递出去
- 在具体的组件上利用connect方法获取store上面的数据,此时数据会传递给组件的props,并且同时会添加一个diapatch方法用于调用reducer相关指令行为
- 通过组件的props访问数据,拿到数据之后就可以在界面上进行渲染了
- 如果想修改数据可以触发props的dispatch方法,将指令和数据传递给reducer进行处理
- reducer会根据指令进行数据更新,更新后会返回给store,store又会传递给组件
redux代码优化
- 可以将reducre进行拆分书写,最后倒入store的时候只要通过combineReducers进行合并即可
- 如果想简化合一将指令的调用单独封为Action函数,并且可以通过bindActionCreators实现自动生成Action
- 可以将Action指令定义为常量(我觉得不需要)
redux中间件
- 从代码层面上中间件就是一个函数,当组件想要修改数据的时候先触发指令,指令要经过中间件再传递给store,之前指令直接传递给store
- 中间件可以使用redux的applyMiddleware进行注册,注册方法是将再cerateStore的第二个参数上执行
- 中间件的作用是将指令拦截下来,在这时进行一些异步操作,完成后依旧把指令交给store
import React from 'react'import ReactDOM from 'react-dom'import App from './App'// 引入createStore创建store仓库啊// 引入applyMiddleware注册中间件import {createStore, applyMiddleware} from 'redux'// reducer数据const state = { name: 'zs'}// reducerfunction reducer(data = state, action) { // 打印指令 console.log(action) // 返回数据 return state}// 中间件 - 本身就是一个函数// 参数1 - store的getState方法,使用这个方法可以获取数据// 参数2 - reducer的dispatch方法,使用这个方法可以调用另一个指令function middel({getState, dispatch}) { // 打印两个参数看一下 console.log(getState(), dispatch) // 中间件二层函数 // 参数是一个方法,可以把指令传递下去 return function(next) { // 中间件三层函数 // 参数为指令信息 return function(action) { // 要执行的异步操作写在三层函数内 setTimeout(() => { console.log('中间件异步操作执行了') console.log(action) // 执行完异步操作把指令传给store next(action) }, 1000) } }}// 创建仓库啊// 第二个参数可以注册中间件,参数为中间件函数const store = createStore(reducer, applyMiddleware(middel))// 调用store的方法模拟传递一个指令store.dispatch({type: 'add'})ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root'))
redux-thunk异步解决方案
reducer是负责计算的,不适合处理异步操作,所以,可以使用redux-thunk处理需要处理的异步操作
// src/index.js 使用thunk做异步处理import React from 'react'import ReactDOM from 'react-dom'import App from './App'import {createStore, applyMiddleware} from 'redux'// 引入createStore创建store仓库,注册中间件import {createStore, applyMiddleware} from 'redux'// 获取要使用的reducerimport reducer from './store/reducer'import {Provider} from 'react-redux'// 引入thunk处理异步import thunk from 'redux-thunk'// 创建仓库// 这里直接把thunk处理为中间件const store = createStore(reducer, applyMiddleware(thunk))ReactDOM.render( <React.StrictMode> <Provider store={store}><App /></Provider> </React.StrictMode>, document.getElementById('root')
// src/store/action/text.js 使用thunk中间件进行异步处理// 引入axios做异步请求import axios from 'axios'// 这里书写的是text组件的指令const action = { // getData指令,这个指令是一个虚拟指令,并没有实际意义,只是作为一个中间件用来触发另一个实际存在的指令 - thunk getData() { // 返回一个函数 return async (dispatch) => { // 函数使用async和await获取数据 const {data} = await axios.get('http://edufront.lagou.com/front/ad/getAdList') // 获取数据成功后调用dispatch触发另一个指令 dispatch({type: 'addType', newDate: [data.message]}) } }}export default action
redux-saga异步解决方案
- 同redux-thunk一样,redux-saga也可以进行异步请求操作,同样是一个中间件,解决了redux导致的Action书写复杂的问题
- saga引入为一个函数,需要执行一次才能作为中间件使用
- saga支持将异步请求单独书写:使用redux-saga/effects中的takeEvery(指令)和put(diapatch)
- index中的saga要接收单独书写的saga文件导出的方法
// src/index.js 使用saga进行异步请求import React from 'react'import ReactDOM from 'react-dom'import App from './App'// 引入createStore创建store仓库,applyMiddleware注册中间件import {createStore, applyMiddleware} from 'redux'// 获取要使用的reducerimport reducer from './store/reducer'import {Provider} from 'react-redux'// 引入redux-sagaimport reduxSaga from 'redux-saga'// 引入reduxSaga中间件需要使用的参数import getData from './store/saga/text.saga'// reduxSaga需要先执行一下才能使用const saga = reduxSaga()// 创建仓库,使用reduxSaga注册中间件const store = createStore(reducer, applyMiddleware(saga))// reduxSaga中间件只用参数saga.run(getData)ReactDOM.render( <React.StrictMode> <Provider store={store}><App /></Provider> </React.StrictMode>, document.getElementById('root'))
// src/store/action/text.js 创建虚拟指令给saga进行捕捉// 使用saga后action就可以按照正常方式书写了const action = { // 指令 getData(){ return {type: 'getData'} }}// 导出指令export default action
// src/Components/text.js 组件不需要改动import {connect} from 'react-redux'import {bindActionCreators} from 'redux'import action from '../store/action/text'function Text(props) { console.log(props) return ( <> <button onClick={() => props.getData()}>点击获取数据</button> <ul> {props.textReducer.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> </> )}const data = (store) => ({ textReducer: store.textReducer})const textAction = dispatch => ({ ...bindActionCreators(action, dispatch)})export default connect(data, textAction)(Text)
// src/store/saga/text.saga.js 新建saga文件单独请求数据// 引入saga中的两个方法// takeEvery用来捕捉指令// put相当于dispatchimport {takeEvery, put} from 'redux-saga/effects'// 引入axios请求数据import axios from 'axios'// 回调函数,同样使用特殊写法function* loaderData() { // 获取数据 const {data} = yield axios.get('http://edufront.lagou.com/front/ad/getAdList') // 调用put调用新指令,并把需要传递的数据加进去 yield put({type: 'upData', data: data.content})}// 直接导出,导出后作为中间件saga的run方法参数// 这里使用ES6特殊语法function*和yield进行异步请求export default function* getData() { // yield相当于return,takeEvery接收两个参数 // 参数1:捕捉的指令名 // 参数2:要执行的回调 yield takeEvery('getData', loaderData)}
// src/store/reducer/text.js reducer只需要书写新指令的函数即可// 原始数据let text = [{id: 1, name: 'zs'}]// const textReducer = (state = text, action) => { switch (action.type) { // saga调用的真正指令,前面的getData可以看作虚拟指令,只是用来给saga捕捉使用 case 'upData': return [ ...action.data ] default: return state }} export default textReducer
redux-saga拆分和合并
当同时存在多个saga需求的时候我们需要使用saga的拆分和合并特点
- saga/effects提供了一个all方法,可以对多个saga进行合并操作
- 最后再index中。saga的run方法参数替换为合并后的rootSaga就可以了
import {all} from 'redux-saga/effects'引入多个sagaexport default function* rootSaga(){ yield all([ 多个saga()执行 ])}
简化action和reducer
利用redux-actions包对action和reducer进行简化
// src/store/Action/action.count.js action文件使用createAction方法将指令全部输出// 引入createAction输出指令import { createAction } from 'redux-actions'// 输出指令export const addCount = createAction('addCount')export const cutCount = createAction('cutCount')export const addCount_N = createAction('addCount_N')
// src/store/Reducer/reducer.count.js // 引入handleActions方法简化reducer,这里把名字修改一下更贴切reducerimport {handleActions as reducer} from 'redux-actions'// 引入需要的指令,后面会用到import {addCount, addCount_N, cutCount} from '../Action/action.count'const data = { count: 0}// 使用handleActions方法书写指令函数,handleActions方法包裹1个对象const countReducer = reducer({ // 对象内部每个属性即为指令名,属性值为一个函数,这个函数返回一个对象,即为要输出的数据 // state为数据,action为触发的指令 [addCount]: (state, action) => ({count: state.count + 1}), // 注意,如果需要穿参数,这里参数默认为action.payload [addCount_N]: (state, action) => ({count: state.count + action.payload}), [cutCount]: (state, action) => ({count: state.count - 1})}, data)// 导出export default countReducer