一、实践笔记来自:技术胖-Redux免费视频教程(24集)
知识体系三:
文章定位:Redux模式了解,很多写法已经过时,【前端笔记】五、Redux Toolkit概述 & 官方模板,axios异步请求还是蛮重要的。
-
技术胖原文:juejin.cn/post/684490…
-
前置文章及环境搭建:React Native & React & Ant D & Redux
-
合理性:React父子传数据太麻烦了,还得写方法传值,各层级互传,让他专注视图层就好了;Redux-好用的数据层框架,redux只做绿线操作,蓝线是自动操作的;
-
Redux借鉴了Flux,但比Flux优秀;
- 基本流程
第3节 Redux及Antd初始化
1、 先到目标文件夹建立一个Redux应用,项目名必须要小写
npx create-react-app reduxdemo
2、准备写一个TodoList组件,保留index.js;其他都可以先删了
- index.js对应没啥用的也删光
3、TodoList类创建与使用
- TodoList本体
import React, { Component } from 'react';
class TodoList extends Component {
state = { }
render() {
return(
<div>Hello World</div>
)
}
}
export default TodoList;
- 调用
import React from 'react';
import ReactDOM from 'react-dom/client';
import TodoList from './TodoList';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<TodoList />
</React.StrictMode>
);
- 引入antd
npm install antd --save- 网速不好的可以用yarn add antd
第四节 Antd美化
1、整体UI类似笔记2,使用ANTD美化
import 'antd/dist/antd.css'貌似没快捷键,新版本会报错说找不到这个文件,从modules里找发现改成了import 'antd/dist/reset.css';- antd包下的Input I大写
- 其中style需要双括号:style={{width:"250px"} },多个属性期间用逗号隔开
class TodoList extends Component {
state = { }
render() {
return(
<div>
<Input
placeholder='Free Hug'
style={{width:"250px"} }/>
</div>
)
}
}
2、 引入 Input,Button,List
- 支持const定义数据
import React, { Component } from 'react';
import 'antd/dist/reset.css';
import { Input,Button,List} from 'antd';
import Item from 'antd/es/list/Item';
const data=[
'香菇滑鸡',
'鱼香肉丝',
'蒸羊羔'
]
class TodoList extends Component {
state = { }
render() {
return(
<div>
{/* 最外层Div加入样式 增加外边距 */}
<div style={{margin:'20px'}}>
<Input
placeholder='Free Hug'
style={{width:"250px",marginRight:"10px"} } //直接在这边加右边距,省的button写style
/>
<Button type='primary' >增加</Button>
</div>
<div
style={{maigin:'10px ', width:'300px'}}
>
<List
bordered
dataSource={data}
/* ES6语法糖解析 antd规范*/
renderItem={Item=>(
<List.Item>
{Item}
</List.Item>
)}
/>
</div>
</div>
)
}
}
export default TodoList;
第五节 创建Redux中的仓库-store和Reducer
- Store统一管理状态
- Action为每个组件发起的事件,reducer通过store传递来的action返回新的state;
- 组件通过subscribe更新新的state;
1、 创建最重要的store
*npm install --save redux
2、src下建立store文件夹
- 文件夹下建立index.js作为管理文件
- 导包
import { createStore } from "redux";被弃用,使用import {legacy_createStore as createStore} from 'redux'
import {legacy_createStore as createStore} from 'redux'
const store =createStore()
/* 暴露出去 */
export default store
- 包下建立reducer.js
- 提供管理状态的方法
//提供方法 管理状态 使用匿名函数返回状态
//定义个默认参数
const defaultState={}
export default(state=defaultState,action)=>{
return state
}
- store内index.js中createStore注入管理了类reducer
import {legacy_createStore as createStore} from 'redux'
import reducer from './reducer'
/* 引入reduceer后 方法注入 */
const store =createStore(reducer)
/* 暴露出去 */
export default store
- 接下来将TodoList的const数据迁移到reducer.js中,通过其方法进行获取
- 定义两个Value
- inputValue:用于placeholder占位
- list:用于每个list展示
//提供方法 管理状态 使用匿名函数返回状态
//定义个默认参数
const defaultState={
inputValue:'sport menu',
list:[
'swim',
'running',
'jump'
]
}
export default(state=defaultState,action)=>{
return state
}
- TodoList中在构造方法里联动store
constructor(props){
super((props))
console.log(store.getState())
}
- 值已经通过引入store,通过其注入的reducer类(exprot default)获取,接下来应该将其与TodoList 构造函数中的state联动
constructor(props){
super((props))
//console.log(store.getState())
this.state=store.getState()
}
- 顺道把input的placeholder改成从state里取
placeholder={this.state.inputValue}
<List
bordered /* 加这个会好看点,有外边框 */
dataSource={this.state.list}
/* ES6语法糖解析 antd规范*/
renderItem={Item=>(
<List.Item>
{Item}
</List.Item>
)}
/>
第六节 Redux Dev Tools的安装
- 可以在控制台中调试数据
- 安装好之后需要配置createStore参数
- 配置官网 Redux DevTools
/* 引入reduceer后 方法注入 */
const store =createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() /* 通过有这个插件就调用 */
)
第七节 通过Input体验Redux的流程
1、和以前一样 定义方法changeInputValue,标签使用onChange={this.changeInputValue} 构造函数对方法的this进行bind绑定,
2、我们的目的是Action要改变store中的值来驱动UI层变化,TodoList中操作
- Action在代码表现上是个对象,在对应方法里定义就可以
- 必须要指定type和value
- type有了之后需要通过dispatch将其关联回store ,store因为有动态注入,此时调用分发方法dispatch即可传给action,驱动数据更新;reducer会自动传值
changeInputValue(el){
const action={
type:'changeInput', /* 命名,如果提示波浪线 一般都是:没写或者写成=了 */
value:el.target.value
}
/* 建立好之后 需要通过dispatch联系发送 */
store.dispatch(action)
}
- reducer把他的值打出来,列表中看到每次有两行,第一行是源数据,第二行是传递的action,有type和value两个属性。
// 俩参数 1是数据源 2是接受的数据
export default(state=defaultState,action)=>{
console.log(state,action)
return state
}
- 此时通过辅助工具查看inputValue值是没变化的(先输入值,然后删光,发现state变了,但UI中inputValue没变)
- 订阅方法:先输入再删光后UI里的也变了,不订阅视图不会更新
constructor(props) {
super((props))
//console.log(store.getState())
this.state = store.getState()
this.changeInputValue = this.changeInputValue.bind(this)
this.storeChange = this.storeChange.bind(this) //转变this指向
store.subscribe(this.storeChange) //订阅Redux的状态
}
storeChange(){
this.setState(store.getState)
}
- 关于setState,初始时以es6方式定义了类数组的state,使用setState若里面有旧的key,则对应key的值会更新;若有新的key,新的key与对应值会加入到state中;
- 控制对应的reducer数据更新
- 针对action.type做特化处理,驱动数据跟新反作给store
- 保持数据源的完整性
- 新版本不用主动subscribe
// 俩参数 1是数据源 2是接受的数据
export default(state=defaultState,action)=>{
console.log(state,action)
//reducer不能污染数据源,针对特点action进行特化数据驱动
if(action.type === 'changeInput'){
let newState = JSON.parse(JSON.stringify(state)) //深度拷贝state
newState.inputValue = action.value
return newState
}
return state
}
- 此时发现第五节改写的从stateinput的值已经是么修改后的action的type了,完成reducer驱动
第八节 通过Input制作TodoList功能
- imput修改后,可以制作add功能
1、 增加点击事件
<Button type='primary' onClick={this.clickBtn}>增加</Button>2、 处理方法
- 方法体设置type,给Reducer判断
- 通过dispatch下发回去
clickBtn(){
//console.log('zelo')
const action ={type:'addItem'}
store.dispatch(action)
}
- 有个上一节绑定遗留的问题,不知道咋解决;能用,但这个视频弃坑【毕竟2019年的,不跟着打了,看看思路】。。。看官方文档吧-见第四篇内容
第九节 用Redux实现ToDoList的删除功能
和第八节差不多,定义 action-deleteItem和对应reducer方法
if(action.type === 'deleteItem' ){
let newState = JSON.parse(JSON.stringify(state))
newState.list.splice(action.index,1) //删除数组中对应的值
return newState }
第十节 代码抽常量ationType.js
第十一 方法抽入变量函数:actionCreators.js
- 箭头表达式:()表参数,({})内是返回值
export const changeInputAction = (value)=>({ type:CHANGE_INPUT, value })
- 代码引入后调用方式
changeInputValue(e){ const action = changeInputAction(e.target.value) store.dispatch(action) }
第十二节 注意别踩坑事项
store必须是唯一的,多个store是坚决不允许,应用只能有一个store空间- 只有
store能改变自己的内容,Reducer不能改变 Reducer必须是纯函数:如果函数的调用参数相同,则永远返回相同的结果。它不依赖于程序执行期间函数外部任何状态或数据的变化,必须只依赖于其输入参数。Reducer里增加一个异步ajax函数,获取一些后端接口数据,然后再返回,这就是不允许的
第十三节 前后端分离
1、 把组件TodoList里的render()的内容抽进一个新组件TodoListUi
- 记得引相关库
- state咋办
- 调用的方法咋办
import React, { Component } from 'react';
class TodoListUi extends Component {
render() {
return (
<div style={{margin:'10px'}}>
<div>
<Input
placeholder={this.state.inputValue}
style={{ width:'250px', marginRight:'10px'}}
onChange={this.changeInputValue}
value={this.state.inputValue}
/>
<Button
type="primary"
onClick={this.clickBtn}
>增加</Button>
</div>
<div style={{margin:'10px',width:'300px'}}>
<List
bordered
dataSource={this.state.list}
renderItem={(item,index)=>(<List.Item onClick={this.deleteItem.bind(this,index)}>{item}</List.Item>)}
/>
</div>
</div>
);
}
}
export default TodoListUi;
2、解决方法 都当属性传过来
render() {
return (
<TodoListUI
inputValue={this.state.inputValue}
list={this.state.list}
changeInputValue={this.changeInputValue}
clickBtn={this.clickBtn}
deleteItem={this.deleteItem}
/>
);
}
3、 UI组件调用方法更新,使用props
import React, { Component } from 'react';
import 'antd/dist/antd.css'
import { Input , Button , List } from 'antd'
class TodoListUi extends Component {
render() {
return (
<div style={{margin:'10px'}}>
<div>
<Input
style={{ width:'250px', marginRight:'10px'}}
onChange={this.props.changeInputValue}
value={this.props.inputValue}
/>
<Button
type="primary"
onClick={this.props.clickBtn}
>增加</Button>
</div>
<div style={{margin:'10px',width:'300px'}}>
<List
bordered
dataSource={this.props.list}
renderItem={(item,index)=>(<List.Item onClick={(index)=>{this.props.deleteItem(index)}}>{item}</List.Item>)}
/>
</div>
</div>
);
}
}
export default TodoListUi;
第十四节 纯UI组件修改为无状态对象函数,提升性能
- 纯UI不含构造方法,可以去掉Component
- 将其改为
TodoListUI对象函数,返回JSX - 因为是函数 可以改为传进来一个
props参数,之后修改里边的所有props,去掉this
第十五节 Axios异步获取数据并和Redux结合
- 获取数据方法写在UI组件中,不影响reducer纯函数
componentDidMount(){
axios.get('https://www.easy-mock.com/mock/5cfcce489dc7c36bd6da2c99/xiaojiejie/getList').then((res)=>{
const data = res.data
const action = getListAction(data)
store.dispatch(action)
})
}
- dispatch后 回传store中修改后的state类型的对象
if(action.type === GET_LIST ){ //根据type值,编写业务逻辑
let newState = JSON.parse(JSON.stringify(state))
newState.list = action.data.data.list //复制性的List数组进去
return newState
}
第十六节 redux-thunk中间件,官方也用了,store引入【Redux的中间件之一】
- 作用:在
Dispatch一个Action之后,到达reducer之前,进行一些额外的操作,就需要用到middleware(中间件)。在实际工作中你可以使用中间件来进行日志记录、创建崩溃报告,调用异步接口或者路由; - 使用增强函数解决插件的冲突:
import { createStore , applyMiddleware ,compose } from 'redux' // 引入createStore方法
import reducer from './reducer'
import thunk from 'redux-thunk'
//原window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
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 //暴露出去
第十七节 Redux-thunk的使用方法
- 目的:这节课我们把向后台请求数据的程序放到中间件中,这样就形成了一套完整的Redux流程,所有逻辑都是在Redux的内部完成的
- 原本十二节里把Action组装抽记进actionCreators.js,UI组件里写的调用方法
- 优势:这个函数可以直接传递
dispatch进去,这是自动的,然后我们直接用dispatch(action)传递就好了。现在我们就可以打开浏览器进行测试了。方法改写:componentDidMount
export const getTodoList = () =>{
return (dispatch)=>{
axios.get('https://www.easy-mock.com/mock/5cfcce489dc7c36bd6da2c99/xiaojiejie/getList').then((res)=>{
const data = res.data
const action = getListAction(data)
dispatch(action)
})
}
}
第十八,十九节 Redux-saga【Redux的另一个中间件之一】
第二十节 React-Redux组件介绍与安装
功能:简化Redux流程,如果你公司不用这个插件,其实没必要耗费时间学。但是作为一篇文章,必须保证知识尽可能完整。(需要注意的是概念:React、Redux、React-redux是三个不同的东西)
- 看了下,官方推荐demo和公司都用了
第二十一,二十二节 React-redux中的Provider和connect
一、Provider提供器:被provide包裹的都能获取到组件store,组件外编写
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './TodoList'
//---------关键代码--------start
import { Provider } from 'react-redux'
import store from './store'
//声明一个App组件,然后这个组件用Provider进行包裹。
const App = (
<Provider store={store}>
<TodoList />
</Provider>
)
//---------关键代码--------end
ReactDOM.render(App, document.getElementById('root'));
二、connect连接器:组件内编写
-
改变组件的export方式
-
建立连接器的映射关系
三、React-redux的数据修改,UI组件响应事件排发
- 当前
export default connect(stateToProps,null)(TodoList);二参为null,通过这个参数才能改变store中的值; - 建立新的映射关系并取值
- 参考代码
import React, { Component } from 'react';
import store from './store'
import {connect} from 'react-redux'
class TodoList extends Component {
constructor(props){
super(props)
this.state = store.getState()
}
render() {
return (
<div>
<div>
<input value={this.props.inputValue} onChange={this.props.inputChange} />
<button>提交</button>
</div>
<ul>
<li>JSPang</li>
</ul>
</div>
);
}
}
const stateToProps = (state)=>{
return {
inputValue : state.inputValue
}
}
const dispatchToProps = (dispatch) =>{
return {
inputChange(e){
console.log(e.target.value)
}
}
}
export default connect(stateToProps,dispatchToProps)(TodoList);
- 到这一步只能实现组件内的UI效果,并没有派发出去修改store
- 派发action到store中,改写dispatchToProps
const dispatchToProps = (dispatch) =>{
return {
inputChange(e){
let action = {
type:'change_input',
value:e.target.value
}
dispatch(action)
}
}
}
- 在reducer中编写处理方法
const defalutState = {
inputValue : 'jspang',
list :[]
}
export default (state = defalutState,action) =>{
if(action.type === 'change_input'){
let newState = JSON.parse(JSON.stringify(state))
newState.inputValue = action.value
return newState
}
return state
}
第二十四节代码优化
1、类似kotlin的精简思路,编写结构变量 把this.props抽出来
render() {
let {inputValue ,inputChange,clickButton,list} = this.props;
return (
<div>
<div>
<input value={inputValue} onChange={inputChange} />
<button onClick={clickButton}>提交</button>
</div>
<ul>
{
list.map((item,index)=>{
return (<li key={index}>{item}</li>)
})
}
</ul>
</div>
);
}
- 将UI组件修改为无状态UI对象,这套框架做到了UI对象内通过connect处理事件把dispatch派发出去了
- 这里发现构造方法里的state没了???没了还能调?,像是有个默认值
constructor(props){
super(props)
this.state = store.getState()
}
import React from 'react';
import {connect} from 'react-redux'
const TodoList =(props)=>{
let {inputValue ,inputChange,clickButton,list} = props; // 粘贴过来后,此处要进行修改
return (
<div>
<div>
<input value={inputValue} onChange={inputChange} />
<button onClick={clickButton}>提交</button>
</div>
<ul>
{
list.map((item,index)=>{
return (<li key={index}>{item}</li>)
})
}
</ul>
</div>
);
}
const stateToProps = (state)=>{
return {
inputValue : state.inputValue,
list:state.list
}
}
const dispatchToProps = (dispatch) =>{
return {
inputChange(e){
let action = {
type:'change_input',
value:e.target.value
}
dispatch(action)
},
clickButton(){
let action = {
type:'add_item'
}
dispatch(action)
}
}
}
export default connect(stateToProps,dispatchToProps)(TodoList);