React 笔记
开发依赖
react 包含react所必须的核心代码
react-dom react渲染在不同平台所需要的核心代码
babel 将jsx转化为react代码的工具
React特点
- 声明式编程
WARNING
特点: 只需要维护自己的状态,当状态改变时,React可以根据最新的状态去渲染我们的UI界面
UI = f(state)
数据发生改变的时候 重新执行render函数 重新去渲染页面
- 组件化开发
- 2.1. 组件化 数据依赖
- 2.2. 组件化 事件绑定
默认情况下,this指向为undefined, react中 是通过JSX编辑,babel编译,最后转化为 js 而babel则开启格模式 因此,默认指向为undefined 在正常DOM操作中,监听函数中的this其实是节点对象 ,react并不是直接渲染真实DOM,我们所编写的button只是语法糖,它的本质React的Element对象;那么在这里发生监听的时候,react在执行函数时并没有绑定this,默况下就是一个undefined
如果在绑定的函数中,想要使用当前对象 需要拿到this
js
<button onClick={this.changeText.bind(this)}></button>
如何封装一个组件
定义一个类(类名大写,组件名必须大写) 实现当前组件的render函数。
js
// 1.定义根组件
class App extends React.Component{
render(){return<h2>hi world</h2>}
}
//2 渲染根组件
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App/>)
- 多平台适配
因为采用了虚拟 DOM(Virtual DOM)技术,这使得 React 可以轻松地在多种平台上渲染 UI 界面,而不需要修改代码。React 把界面抽象成一个个组件,组件可以独立地在不同平台上渲染,只需要实现不同平台的 UI 元素即可。
ReactNative
ReactVR
初识JSX
什么是JSX
JSX是一种JavaScript的语法扩展 它用于描述我们的UI界面,并且其完成可以和JavaScript融合在一起使用
3.1 JSX的书写规范
- 必须有根元素
- 推荐使用()包裹
- 单标签/双标签
this的绑定
- 默认绑定 独立执行 foo()
- 隐式绑定 被一个对象执行 obj.foo() ---> obj
- 显式绑定 call/apply/bind foo.call('aaa') ---> String('aaaa')
- new绑定 new Foo() -> 创建一个新的对象 并且赋值给this
如何解决
bind 给btnClick显示绑定this 使用
js
{/* 1.this绑定方式一: bind绑定 */}
<button onClick={this.btn1Click}>按钮1</button>
{/* 2.this绑定方式二: ES6 class fields */}
<button onClick={this.btn2Click}>按钮2</button>
{/* 3.this绑定方式三: 直接传入一个箭头函数(重要) */}
<button onClick={() => console.log("btn3Click")}>按钮3</button>
<button onClick={() => this.btn3Click()}>按钮3</button>
事件传递参数
推荐 箭头函数
js
<button onClick={(e)=>this.btn.handClick(e,'kros',18)}></button>
条件渲染
vue中为v-if v-show
react中 所有的条件判断都和普通的js代码一致
- 条件判断语句 适合逻辑较多的情
- 三元运算符 适合逻辑比较简单
- 与运算符&& 适合如果条件成立,渲染一个组件,若不成立,则不渲染
- v-show的效果 控制display是否为none
JSX本质
jsx 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖 所有的jsx最终都会被转换成React.createElement的函数调用。
createElement需要传递三个参数 type 例如 div|组件名称 config children
渲染逻辑本质上与UI逻辑存在了太高的耦合(内在耦合), 它们之间密不可分,因此react没有将其分到不同的文件中,而是将其组合起来 ==> 组件 组件又是一个类 类又是js 就是相当于jsextensition
虚拟DOM的创建过程
react利用ReactElement对象组成了一个JS对象树 js对象树 就是 虚拟DOM (Virtual DOM)
jsx --转化对应的ReactElement函数的调用 形成 reactElement对象 ---> 渲染 最终形成 真实DOM
vue中 template 解析完后 --> render函数 调用大量的h函数 变成一个一个对象 对象和对象之间的联系最终形成虚拟DOM --> 真实DOM
从源码来看 vue没有react那么直观 vue底层做了很多的事情 v-for v-if v-model 等等 react直接交给babel去解析 vue则自己解析
虚拟DOM跨平台渲染
声明式编程
虚拟DOM帮助我们从命令式编程转到了声明式编程的模式
UI以以一种理想化或者虚拟化的方式保存在内存中,并且它是一个相对简单的JS对象 我们可以通过ReactDOM.render让虚拟DOM和真实DOM同步起来,这个过程中叫做协调 (Reconciliation)
虚拟DOM 同步成为 真实DOM的过程
craco crate-react-app config
React 组件化
根据组件的定义 函数组件 | 类组件 组件内部是否有状态需要维护 无状态 | 有状态 组件 组件的不同职责 展示型组件(Presentational Component) | 容器型 (Container Component)
类组件
类组件定义的前提
组件名称大写开头
类组件需要继承React.Component
类组件必须实现render函数
也可使用es6语法 中的class定义一个组件
js
export default class Test extends React.Component {
constructor(props) {
// 这里----初始化值设置 ---> 可选项
}
render() {
return(
<div>aaa</div>
<div>vvc</div>
<div>xxx</div>
)
}
}
render函数返回值
当其重新渲染之前肯定会先进行一些条件的判断(props state的变化)并返回以下类型之一
- React 元素:
- jsx创建
- react渲染的DOM节点 | 自定义组件
- 数组 | fragments 使用render方法可以返回多个元素
- Portals 可以渲染子节点到不同的DOM子树中
- 字符串或数值类型 在DOM中会被渲染为文本节点
- 布尔类型或null 什么都不渲染
函数组件
使用fucntion
进行定义的函数
无状态组件 没有生命周期 但也会被更新并挂载 没有组件实例 所以this不能指向组件实例
类组件通过生命周期等实现的功能 在函数组件内则用其 对应的hooks去实现
js
const function App(){
return (
<h1>helloWorld~ </h1>
)
}
export default App
生命周期
TIP
简而言之,和人的生命时间线一致, 合适的阶段做 做合适的事情
- 装载Mount
- 更新Update
- 卸载UnMount
1.装载Mount
INFO
生命周期函数的介绍
conStructor
如果不初始化state或不进行方法绑定 则无需为react组件实现构造函数 constructor中 只做2件事情
- 通过this.state 赋值 初始化state数据
- 事件绑定实例(this)
componentDidMount
会在组件挂载后(完成DOM渲染后) 立即调用 该函数中可以执行的操作
- 发送网络请求
- DOM操作 | 在此添加订阅 willUnMount中取消订阅
componentDidUpdate
会在DOM更新后 立即调用 首次渲染不执行
该函数中可以执行的操作
- 发送网络请求
- DOM操作
componentWillUnmount
会在组件卸载及销毁之前直接调用 该函数中可以执行的操作
- 执行必要的清理操作 例如 清理定时器
- 卸载组件
不常用的生命周期
getDerivedStateFromProps
state的值在任何时候都依赖props使用 该方法返回一个对象来更新state
getSnapshotBeforeUpdate
dom更新前 回调的一个函数 可以获取DOM更新前的一些信息(滚动位置 )
shouldComponentUpdate
通过返回值决定是否重新渲染 在这里比较 nextProps ,nextState 和this.state 返回true 更新 false 不更新
2.更新Update
3.卸载UnMount
组件通信
1.父组件传值给子组件
props
父组件 通过属性=值的形式来传递给子组件数据 子组件 通过props参数获取父组件传递过来的数据
参数验证propTypes
v15.5 已经移入另一个包
js
import React, { Component } from 'react'
import PropTypes from "prop-types"
export class MainBanner extends Component {
render() {
const { banners,title } = this.props
return (
<div>
<h2>商品列表{title}</h2>
<ul>
{
banners.map(item => {
return <li key={item.acm}>{item.title}</li>
})
}
</ul>
</div>
)
}
}
MainBanner.propTypes = {
banners:PropTypes.array,
title:PropTypes.string,
}
export default MainBanner
2.子组件传值给父组件
js
export default function App() {
const [title] = useState(['流行','新歌','精选']);
const [tabIndex,setTabIndex] = useState(0);
const tabClick=(index)=>{
setTabIndex(index)
}
return (
<div>
{/* 事件函数 tabClick 将值赋给子组件 */}
<TabControl title={title} tabClick={(index)=>tabClick(index)} />
<h1>{title[tabIndex]}</h1>
</div>
)
}
// 子组件
export default function TabControl(props) {
const { title } = props;
const [currentIndex, setcurrentIndex] = useState(0);
const change = (index) => {
setcurrentIndex(index);
props.tabClick(index);
// 通过props.父组件设置的属性 将值index传递过去
}
return (
<div className='tab-control'>
{
title.map((item, index) => {
return (
<div className={`item ${index === currentIndex ? 'active' : ''}`}
onClick={() => change(index)} key={item}>
<span className='text'>{item}</span>
</div>
)
})
}
</div>
)
}
React插槽(slot)
组件的children子元素
props属性传递给React元素
js
import React from 'react'
import Navbar from './Navbar'
import Navbb from './Navbb'
export default function App() {
return (
<div className='app'>
<Navbar>
<button>按钮</button>
<h2>标题</h2>
<i>wenzi</i>
</Navbar>
<Navbb leftSlot={<button>daiwo</button>}
centerSlot={<h2>标题</h2>}
></Navbb>
</div>
)
}
// navbar
export default function Navbar(props) {
const {children} = props;
return (
<div className='nav-bar'>
<div className='left'>{children[0]}</div>
<div className='center'>{children[1]}</div>
<div className='right'>{children[2]}</div>
</div>
)
}
// navbb props实现
export default function Navbb(props) {
const {leftSlot, centerSlot,rightSlot} =props;
return (
<div>
<div className='left'>{leftSlot}</div>
<div className='center'>{centerSlot}</div>
<div className='center'>{rightSlot}</div>
</div>
)
}
3.context 非父子组件数据共享
在开发中,比较常见的数据传递方式是通过props属性自上而下(由父到子)进行传递
但是对于有一些场景:比如一些数据需要在多个组件中进行共享(地区偏好、UI主题、用户登录状态、用户信息等)。
如果我们在顶层的App中定义这些信息,之后一层层传递下去,那么对于一些中间层不需要数据的组件来说,是一种冗余的操作。
若多层级传递,则代码非常冗余 React提供了Context Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props; Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言;
基本使用
- 创建Context,
- Context.Provider提供了value
- 类组件contextType = Context
- 类组件 this.context
js
// 1.单独设置一个js用于 创建Context
import React from "react";
const ThemeContext = React.createContext();
export default ThemeContext;
//
import ThemeContext from './context/theme-context';
<h2>App</h2>
// 2. Provider提供value
<Usercontext.Provider value={{nickname: "kobe", age: 30}}>
<ThemeContext.Provider value={{color: "red", size: "30"}}>
<Home {...info} />
<Profile/>
</ThemeContext.Provider>
</Usercontext.Provider>
// 例如在Home中嵌套homeInfo
//homeINfo内姐搜Prover提供的值
import React, { Component } from 'react'
import ThemeContext from './context/theme-context'
export class HomeInfo extends Component {
render() {
// 4.第四步操作: 获取数据, 并且使用数据
console.log(this.context)
return (
<div>
<span>我是homeinfo:{this.context.color}</span>
</div>
)
}
}
// 3.第三步操作: 设置组件的contextType为某一个Context
HomeInfo.contextType = ThemeContext
export default HomeInfo
4.eventBus 非父子组件通信 事件总线
事件总线 eventBus 这里与vue中相似
使用步骤如下
- 安装插件
npm install hy-event-store | yarn add hy-event-store
- 创建
eventBus.js
文件
js
import {HYEventBus} from 'hy-event-store';
const eventBus = new HYEventBus();
export default eventBus;
3.子组件 发布事件 eventBus.emit('事件名称','参数1','参数2');
js
// 跨级子组件中 触发司机啊
import React from 'react'
import eventBus from '../utils/event-bus'
export default function HomeBanner() {
const prevClick =()=>{
console.log('上一个');
eventBus.emit('bannernav','tian',111);
}
const nextClick =()=>{
console.log('下一个 ');
eventBus.emit('bannerNext','kros',222)
}
return (
<div>
<h2>HomeBanner</h2>
<button onClick={() => prevClick()}>上一个</button>
<button onClick={() => nextClick()}>下一个</button>
</div>
)
}
- 父级中通过
eventBus.on('事件名')
接收 |订阅事件
js
//父级 在home中发送 数据 通知app中更改数据
import React, { useState, useEffect } from 'react'
import Home from './Home'
import eventBus from './utils/event-bus'
const App = () => {
const [state, setState] = useState({name: '',age: 0});
useEffect(() => {
eventBus.on("bannerPrev", (name, age) => {
console.log("app中监听到bannerPrev", name, age)
setState({ name, age, height })
})
eventBus.on("bannerNext", (info) => {
console.log("app中监听到bannerNext", info)
});
return () => {
// off('事件名') 取消事件订阅
eventBus.off("bannerPrev");
}
}, []);
const { name, age, height } = state;
return (
<div>
<h2>App Component: {name}-{age}</h2>
<Home />
</div>
);
}
export default App;
setState
React中 没有像vue一样 通过数据劫持 Object.defineProperty (getter, setter)等来监听数据的变化 必须通过setState来告知React数据已经发生了变化 从源码中可知 setState是从Component中继承过来的 ,也可通过 来判断 setState 是先存进 state 队列还是直接更新,如果值为 true 则执行异步操作,为 false 则直接更新。一般认为,做异步设计是为了性能优化、减少渲染次数。 一般会被追问(也可以自己说):在什么情况下 isBatchingUpdates 会为 true 呢?
- 在 React 可以控制的地方,就为 true,比如在 React 生命周期事件和合成事件中,都会走合并操作,延迟更新的策略。 setState是异步操作 我们并不能在执行完setState之后就立马拿到最新的state结果
18版本后 setState为异步了
为什么setState设计为异步?
参考答案
setState设计为异步,可以显著的提升性能,
- 如果每次调用setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,效率很低,
- 最好的办法是 获取到多个更新,之后进行批量更新 如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步 state和props不能保持一致性,会在开发中产生很多的问题
获取更新后的值
setState(partialState, callback) setState({message:'coder'},()=>{console.log(message)})
js
export default class App extends Component {
constructor(){
super();
this.state = {
message:'hixiao',
counter:0
}
}
change(){
// 1.第一种
// this.setState({message:'2222'})
// 2.第二种 可以在回调函数中编写新的state的逻辑
// 当前的回调函数会将之前的state、props传递过来
/* this.setState((state,props)=>{
console.log(this.state.message,this.props);
return{
message:"你好呀,小虎队的"
}
}) */
// 3. setState在react中的事件处理是一种异步调用
// 这个操作不会立马完成,需要一定的时间后才能真正的完成
// r如果需要在数据更新后(数据合并),获取到对应的结果并执行逻辑代码
// 可以在setState中传入第二个参数,callback
this.setState({ message:"kornssss" },()=>{
console.log('++++++++',this.state.message);
});
console.log('-----------',this.state.message); // 这里的this.state.message为之前 上次的值
}
/**
* Object.assign(target,...sources) -->(目标对象 ,源对象)
* 实行的是浅拷贝
* 源码中 通过Object.assign(this.state,newState) 将其合并
* 合并时,键名相同,值不同,则合并后 该键的值为源对象的值 且目标对象的值也被改变
* 意思为 :
* newState覆盖原来的state 然后在合适的位置调render()方法
*/
render() {
const {message, counter} = this.state;
return (
<div>App
<h2>{message}----------{counter}</h2>
<button onClick={()=>this.change()}>change</button>
</div>
)
}
}
setState一定是异步的嘛?
这里分为 18前 18后
- 在18之前
在组件生命周期或React合成事件中,setState是异步; 在setTimeout或者原生dom事件中,setState是同步 在 promises, setTimeout, native event handlers, 中的更新为 同步操作 - 18之后
若在18之后需要同步的拿到,可以利用flushSync
js
import {flushSync} from 'react-dom'
changeText() {
setTimeout(() => {
// 18之前 这里是同步操作 如果想要在18之后 在这里实现同步的效果
flushSync(()=>{
this.setState({ message: "你好啊,xiaotian~" })
})
console.log(this.state.message)
}, 0);
}
React更新过程
在React中 props | state
的改变--->引起render函数重新执行--->产生新的DOM树 同时,diff算法对 改变的DOM树进行 新旧比较 ---> 找出差异 并更新---> 更新真实的DOM
一棵树对比另一颗树 进行完全对比更新,复杂度为O(n^2) n为树中的元素数量 若如此的话,React展示10000个元素则 对比产生计算量为数十亿的范围
React对这个算法进行了优化,将其优化成了O(n),
同层节点之间相互比较,不会垮节点比较;
不同类型的节点,产生不同的树结构;
开发中,可以通过key来指定哪些节点在不同的渲染下保持稳定;
注意
key的优化
- 在最后位置柴数据 这种情况意义不大
- 前面插入数据 在没有key的情况下,则所有的li元素都需要进行修改
- 当子元素(这里的li)拥有 key 时,React 使用 key 来匹配原有树上的子元素以及最新树上的子元素
- key为111 222的元素仅进行位移, 不需要进行任何修改
- key为333的则插入到最前面即可 key的注意 key为唯一 | key不要使用随机数 | 使用index作为key,对性能是没有优化的
关于render函数被调用
众所周知,当组件中数据发生改变后,所有的组件都需重新render,进行diff算法 -->导致效率太低
然而很多的组件并非需要重新render
调用render渲染的前提---> 依赖(state,props)发生改变时,调用props
- shouldComponentUpdate() 返回值为Boolean, true 则 进行组件更新, false 则 不更新 一般接收2个参数(nextProps,nextState)
js
shouldComponentUpdate(newProps, nextState) {
// 自己对比state是否发生改变: this.state和nextState
if (this.props.message !== newProps.message) {
return true
}
return false
}
- 纯组件 PureComponent
在类组件中, 将之前的Component替换为PureComponent后,则无需进行手动的SCU内条件判断 是否更新 PureComponent 内部自动实现了 shouldComponentUpdate 钩子,不需要手动比较 原理:纯组件内部通过分别 对比 前后两次 props 和 state 的值,来决定是否重新渲染组件 纯组件内部的对比是 shallowEqual 的浅层对比
js
import React, { PureComponent } from 'react'
export class Home extends PureComponent {
constructor(props) {
super(props)
this.state = {
friends: []
}
}
// shouldComponentUpdate(newProps, nextState) {
// // 自己对比state是否发生改变: this.state和nextState
// if (this.props.message !== newProps.message) {
// return true
// }
// return false
// }
render() {
console.log("Home render")
return (
<div>
<h2>Home Page: {this.props.message}</h2>
</div>
)
}
}
export default Home
-------------源码中 关于PureComponent实现过程 ------------------------
在
src/ReactBaseClasses.js
下 的PureComponent函数中,在给PureComponentPrototype上的作一个表示isPureReactComponent
为true 看你是否为 PureComponentreact-reconciler\src\ReactFiberClassComponent.new.js
里的checkShouldComponentUpdate
函数中,首先进行一个判断, 如果组件实例的shouldComponentUpdate为函数---> 则去判断是否为PureReactComponent --> 是的话 就去进行shallowEqual
对比在shallowEqual中 首先会判断 oldState 、newState是否为同一个
- 如果是同一个 则直接返回true --> shallowEqual取反返回 false 则不进行更新
- 若不是 ---> 进行下面的 对state里取到的值进行遍历 判断取到的值是否为同一个对象
- --> 是--> true
- --> 不是 --> false
- shallowEqual 根据上面的返回值进行取反 判断是否进行更新
函数式组件中,使用高阶组件memo,通过memo函数进行一层包裹,
js
import {memo} from 'react'
const Profile = memo((props) => {
console.log("profile render")
const {message} = props;
return (
<div>
<h2>这里是app传Profile的值 :{message}</h2>
</div>
)
})
export default Profile
Ref使用
1.ref使用场景
在以下情况下 需要获取DOM进行操作
- 访问DOM元素 (直接访问和操作 DOM 元素的属性)
- 管理焦点、文本选择或媒体播放
- 触发强制动画
- 集成第三方DOM库 (将React组件与需要直接访问DOM的其他库集成)
- 实现滚动位置或动画(实现平滑滚动或根据滚动位置触发动画)
2.创建ref的方式
- 传入字符串
js
<h2 ref="tian">Hello World</h2>
console.log(this.refs.tian);
- 传入一个对象
js
import React, { createRef } from 'react'
this.titleRef = createRef();
<h2 ref={this.titleRef}>hi,xiaotian~</h2>
console.log(this.titleRef.current);
- 传入一个函数
js
this.titleEl = null;
<h2 ref={el => this.titleEl = el}>xiaotian</h2>
console.log(this.titleEl);
3.ref的类型
当ref属性用于HTML元素时,构造函数中使用React.createRef()创建的ref接收底层DOM元素作为其current属性;
当ref属性用于自定义class组件时,ref对象接收组件的挂载实例作为其current属性;
提示
函数式组件是没有实例的,所以无法通过ref获取实例 此时可以通过 React.forwardRef实现
js
import React, { forwardRef } from 'react'
const Hellow = forwardRef(function(props,ref){
return (
<div>
<h1 ref={ref}>这是函数式组件的element</h1>
</div>
)
})
// 父组件中
this.hwRef = createRef();
<Hellow ref={this.hwRef} />
受控&&非受控
绑定value属性并且value是绑定了state中的某个值之后就成为了受控组件 使用非受控组件,这时表单数据将交由 DOM 节点来处理 一个受控组件中,表单数据是由 React 组件来管理的; 两者区别: 受控组件依赖于状态, 受控 ----> 状态和程序中保持一致性
非受控---> 通过ref保留目标组件的引用,通过组件的引用获取组件内部状态 但是无法修改组件内部状态
下面为非受控组件的demo案例
js
import React,{useState, useRef} from 'react';
const [intro] = useState(['tian']);
const intoRef = useRef(null);
const getInto =()=>{
console.log(intoRef.current.value);
}
<div>
<input type="text" defaultValue={intro} ref={intoRef} />
<button onClick={getInto}>获取非受控组件的值</button>
</div>
js
state ={
usename:'213'
}
inputChange(e){
console.log('input',e.target.value);
this.setState({usename:e.target.value},()=>{
console.log(this.state.usename);
});
}
render() {
const {usename} = this.state;
return (
<div>
<h2>{usename}</h2>
{/* 绑定value属性并且value是绑定了state中的某个值之后 就成为了受控组件 */}
<input type='text' value={usename} onChange={(e)=>this.inputChange(e)} />
</div>
)
}
下面给出常用的一些Input 、checked 单选&多选 、 select单选、多选的受控写法
类组件式写法如下
js
import React, { PureComponent } from 'react'
export class App extends PureComponent {
constructor() {
super()
this.state = {
username: "",
password: "",
isAgree: false,
hobbies: [
{ value: "sing", text: "唱", isChecked: false },
{ value: "dance", text: "跳", isChecked: false },
{ value: "rap", text: "rap", isChecked: false }
],
fruit: ["orange"]
}
}
handleSubmitClick(event) {
// 1.阻止默认的行为
event.preventDefault()
// 2.获取到所有的表单数据, 对数据进行组件
console.log("获取所有的输入内容")
console.log(this.state.username, this.state.password)
const hobbies = this.state.hobbies.filter(item => item.isChecked).map(item => item.value)
console.log("获取爱好: ", hobbies)
// 3.以网络请求的方式, 将数据传递给服务器(ajax/fetch/axios)
}
handleInputChange(event) {
this.setState({
[event.target.name]: event.target.value
})
}
handleAgreeChange(event) {
this.setState({ isAgree: event.target.checked })
}
handleHobbiesChange(event, index) {
const hobbies = [...this.state.hobbies]
hobbies[index].isChecked = event.target.checked
this.setState({ hobbies })
}
handleFruitChange(event) {
const options = Array.from(event.target.selectedOptions)
const values = options.map(item => item.value)
this.setState({ fruit: values })
// select 添加 multiple 变为多选
// 额外补充: Array.from(可迭代对象) 将类数组对象转换为数组
// Array.from(arguments)
const values2 = Array.from(event.target.selectedOptions, item => item.value)
console.log(values2)
}
render() {
const { username, password, isAgree, hobbies, fruit } = this.state
return (
<div>
<form onSubmit={e => this.handleSubmitClick(e)}>
{/* 1.用户名和密码 */}
<div>
<label htmlFor="username">
用户:
<input
id='username'
type="text"
name='username'
value={username}
onChange={e => this.handleInputChange(e)}
/>
</label>
<label htmlFor="password">
密码:
<input
id='password'
type="password"
name='password'
value={password}
onChange={e => this.handleInputChange(e)}
/>
</label>
</div>
{/* 2.checkbox单选 */}
<label htmlFor="agree">
<input
id='agree'
type="checkbox"
checked={isAgree}
onChange={e => this.handleAgreeChange(e)}
/>
同意协议
</label>
{/* 3.checkbox多选 */}
<div>
您的爱好:
{
hobbies.map((item, index) => {
return (
<label htmlFor={item.value} key={item.value}>
<input
type="checkbox"
id={item.value}
checked={item.isChecked}
onChange={e => this.handleHobbiesChange(e, index)}
/>
<span>{item.text}</span>
</label>
)
})
}
</div>
{/* 4.select */}
<select value={fruit} onChange={e => this.handleFruitChange(e)} multiple>
<option value="apple">苹果</option>
<option value="orange">橘子</option>
<option value="banana">香蕉</option>
</select>
<div>
<button type='submit'>注册</button>
</div>
</form>
</div>
)
}
}
export default App
函数式组件写法如下
js
import React,{useState, useRef} from 'react'
function App() {
const [username,setUsername] = useState('');
const [password,setPassword] = useState('');
const [isAgree,setIsAgree] = useState(false);
const [hobbies,setHobbies] = useState([
{ value: "sing", text: "唱", isChecked: false },
{ value: "dance", text: "跳", isChecked: false },
{ value: "rap", text: "rap", isChecked: false }
]);
const [fruit,setfruit] = useState(['orange']);
const [intro] = useState(['tian']);
const intoRef = useRef(null);
const handleSubmitClick=(event)=>{
event.preventDefault();
// console.log(event.target);
console.log(username,password);
const checkedHobbies = hobbies.filter(item=>item.isChecked).map(item=>item.value);
console.log('爱好',checkedHobbies);
};
const handleInputChange =(e)=>{
if(e.target.name==='username'){
setUsername(e.target.value);
}else {
setPassword(e.target.value);
}
}
const handleAgreeChange =(e)=>{
setIsAgree(e.target.value);
}
const handleHobbiesChange =(e,index)=>{
const newHobbies = [...hobbies];
newHobbies[index].isChecked = e.target.checked;
setHobbies(newHobbies);
}
const handleFruitChange =(e)=>{
const options =Array.from(e.target.selectedOptions);
const values = options.map(item=>item.value);
setfruit(values);
console.log(values);
}
const getInto =()=>{
console.log(intoRef.current.value);
}
return (
<div>
<form onSubmit={handleSubmitClick}>
<div>
<label htmlFor='username'>
用户:<input type='text' name='username' value={username} onChange={handleInputChange} />
</label>
<label htmlFor='password'>
用户:<input type='password' name='password' value={password} onChange={handleInputChange} />
</label>
</div>
<label htmlFor='agree'>
同意<input type='checkbox' id='agree' checked={isAgree} onChange={handleAgreeChange} />
</label>
{/* checkbox */}
<div>
爱好:
{
hobbies.map((item,index)=>{
return(
<label htmlFor={item.value} key={item.value}>
<input
type="checkbox"
id={item.value}
checked={item.isChecked}
onChange={e => handleHobbiesChange(e, index)}
/>
<span>{item.text}</span>
</label>
)
})
}
</div>
{/* 4.select */}
<div>
<select value={fruit} onChange={e =>handleFruitChange(e)} multiple>
<option value='apple'>苹果</option>
<option value='orange'>橘子</option>
<option value='banana'>香蕉</option>
</select>
</div>
<div>
<input type="text" defaultValue={intro} ref={intoRef} />
<button onClick={getInto}>获取非受控组件的值</button>
</div>
<hr />
<button type='submit'>注册</button>
</form>
</div>
)
}
export default App
高阶组件(函数)
1.什么是HOC ???
在我们JS中很多常见的高阶函数 例如--> map 、 filter 、reduce .
定义: ⾼阶组件(HOC)是React中⽤于复⽤组件逻辑的⼀种⾼级技巧。HOC⾃身不是ReactAPI的⼀部分,它是⼀种基于 React 的组合特性⽽形成的设计模式。
简单点说,就是组件作为参数,返回值也是组件的函数,它是纯函数,不会修改传⼊的组件,也不会使⽤继承来复制其⾏为。相反,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作⽤。
2.使用HOC的原因
- 抽取重复代码,实现组件复用,相同功能组件复用
- 条件渲染,控制组件的渲染逻辑(渲染劫持):权限控制
- 捕获、劫持被处理组件的生命周期,常见场景: 组件渲染性能追踪、日志打点
3. 应用一 props增强
- 添加新的props
enhancedUserInfo文件 注入props
js
import React, { PureComponent } from 'react'
function enhancedUserInfo(OriginComponent){
class NewComponent extends PureComponent {
constructor(props){
super(props)
this.state = {userInfo:{ name:'xiaotian',age:'18'}}
}
render() { // 注入props
return <OriginComponent {...this.props} {...this.state.userInfo} />
}
}
return NewComponent
}
export default enhancedUserInfo
App.js中使用则可以获取到新增props的值
js
import React, { PureComponent } from 'react'
import enhancedUserInfo from './hoc/enhanced-props';
import About from './page/About';
const Profile = enhancedUserInfo(function Profile(props){
return <h1>Profile----{props.name}---{props.age}---{props.names}</h1>
})
export class App extends PureComponent {
render() {
return (<div>
<Profile names={'这个是 ...this.props'} />
<hr />
<About />
</div>
)
}
}
export default App
2.例如使用高阶组件来共享Context
js
import ThemeContext from "../context/theme_context"
function withTheme(OriginComponent){
return props=>{
return (
<ThemeContext.Consumer>
{value=>{return <OriginComponent {...props} {...value} />}}
</ThemeContext.Consumer>
)
}
}
export default withTheme
这里则为使用高阶场景 App.js中
js
import React, { PureComponent } from 'react'
import ThemeContext from './context/theme_context'
import Product from './page/Product'
export class App extends PureComponent {
render() {
return (
<ThemeContext.Provider value={{color:'red',size:'22px'}}>
<Product />
</ThemeContext.Provider>
)
}
}
export default App
Product中的使用
js
import React from 'react'
import withTheme from '../hoc/with_theme'
function Product(props) {
const {color,size} = props;
return (
<div>Product----{color}-----{size}</div>
)
}
export default withTheme(Product)
3. 应用二 渲染判断鉴权
使用高阶组件进行鉴权操作的判断
登录成功- ----> 进入 未登录------> login界面
js
// 主app.js中
import React, { PureComponent } from 'react'
import Cart from './page/Cart'
export class App extends PureComponent {
// constructor(){
// super()
// // this.state = { islogin:false }
// }
loginClick(){
localStorage.setItem('tokens','tian')
// this.setState({isLogin:true})
// 假如这里没有state 还有一种方式即可进行重新渲染
this.forceUpdate();
}
render() {
return (
<div>
App ---
<button onClick={e=>this.loginClick(e)}>点我登录</button>
<Cart />
</div>
)
}
}
export default App
鉴权文件
js
function loginAuth(Origincomponent){
return props =>{
//在此处进行判断
const token = localStorage.getItem('tokens');
if(token) {
return <Origincomponent {...props} />
}else {
return <h2>请先登录</h2>
}
}
}
export default loginAuth
成功则展示的界面
js
import React, { PureComponent } from 'react'
import loginAuth from '../hoc/login_auth'
export class Cart extends PureComponent {
render() {
return (
<div>
<h3>我是Cart的主要内容</h3>
</div>
)
}
}
export default loginAuth(Cart)
3. 应用三 生命劫持
利用HOC来劫持生命周期,在生命周期中完成想要操作的逻辑
js
// import React, { useEffect, useRef } from 'react';
// const App = () => {
// const beginTimeRef = useRef(new Date().getTime());
// useEffect(() => {
// const endTime = new Date().getTime();
// const interval = endTime - beginTimeRef.current;
// console.log(`时间为${interval}ms`);
// });
// return (
// <div>
// <h2>Detail Page</h2>
// <ul>
// <li>数据列表1</li>
// <li>数据列表2</li>
// <li>数据列表3</li>
// <li>数据列表4</li>
// <li>数据列表5</li>
// </ul>
// </div>
// );
// };
// export default App;
import React, { PureComponent } from 'react'
import logRenderTime from './hoc/logRenderTime'
export class App extends PureComponent {
render() {
return (
<div>
<h2>Detail Page</h2>
<ul>
<li>数据列表1</li>
<li>数据列表2</li>
<li>数据列表3</li>
<li>数据列表4</li>
<li>数据列表5</li>
</ul>
</div>
)
}
}
export default logRenderTime(App)
生命劫持的HOC --logRenderTime
js
import { PureComponent } from "react";
function logRenderTime(OriginComponent) {
return class extends PureComponent {
UNSAFE_componentWillMount() {
this.beginTime = new Date().getTime()
}
componentDidMount() {
this.endTime = new Date().getTime()
const interval = this.endTime - this.beginTime
console.log(`当前${OriginComponent.name}页面花费了${interval}ms渲染完成!`)
}
render() {
return <OriginComponent {...this.props}/>
}
}
}
export default logRenderTime
HOC--------------------------forwardRef实现过程-----------------------------------------
初始
function HelloWorld(props){}
js
function HelloWorld(props,ref){
return <h2 ref={ref}></h2>
}
forwardRef(HelloWorld)
函数式组件 没有实例 不能将ref 绑定给组件实例
<HelloWorld ref={this.ref} />
ref 传给组件后 一旦forwardRef 相当于返回了一个新组件
经过forwardRef包裹之后 将ref变为了Helloward函数的第二个参数 将ref单独抽离出去
此时 ref={this.ref} 传给了新组件 到时候在内部 用helloworld调的时候 就不再是传一个参数,
而是传2个参数, 到时候在内部拿到ref 绑定到某个元素上
所以本质上就是把原来的操作劫持掉 内部单独取出来ref
Portals
TIP
应用场景---->希望渲染的内容独立于父组件 独立于当前挂载到的DOM元素中
Portals 提供了将子节点渲染到存在于父组件以外的DOM元素上
React.createPortal(child,container) 接收2个参数,
- 第一个参数为 渲染的子元素
- 第二个参数为 DOM元素
例如 这里创建一共Demo案例
js
import React, { PureComponent } from 'react'
import { createPortal } from "react-dom"
import Modal from './Modal'
export class App extends PureComponent {
render() {
return (
<div>
<h1>App H1</h1>
{
createPortal(<h2>app H2</h2>,document.querySelector('#tian'))
}
<Modal>
<h2>我是Portal</h2>
<p>这里是我的内容</p>
</Modal>
</div>
)
}
}
export default App
Modal中
js
import { PureComponent } from 'react'
import { createPortal } from "react-dom"
export class Modal extends PureComponent {
render() {
return createPortal(this.props.children, document.querySelector("#modal"))
}
}
export default Modal
index.html中 新增DOM元素节点
html
<div id="root"></div>
<div id="tian"></div>
<div id="modal"></div>
实现效果
Fragment && StrictMode
1. Fragment
- fragement应用情景 例如, 一般开发中,在一个组件中返回内容会被div元素包裹,如果不想要被包裹
- 使用Fragment则可以实现
在不需要map遍历等操作时 则可以省略 简写为 <> </>
若在Fragment中添加key则不能使用上面的简写方式了 下面为Fragment简单使用
js
import React, { PureComponent, Fragment } from 'react'
export class App extends PureComponent {
constructor() {
super();
this.state = {
section: [
{ title: "小书包", content: '我是小书包' },
{ title: "小白兔", content: '我是小白兔' },
]
}
}
render() {
const { section } = this.state;
return (
<>
<h2>这里是 Fragment</h2>
<p>不想要返回内容包裹一个div元素可以使用fragement</p>
<hr />
{section.map(item => {
return (
<Fragment key={item.title}>
<h2>{item.title}</h2>
<p>{item.content}</p>
</Fragment>
)})}
</>
)
}
}
export default App
2. StrictMode
StrictMode也就是严格模式, 用来突出显示应用程序中的潜在问题
与Fragment一样,不会渲染到任何可见的UI界面
检查仅在开发模式下运行,它们不会影响生产构建
可为局部 、全局的程序 启动严格模式
StrictMode检查什么的?
- 识别不安全的生命周期
- 使用过时的API
- 检查意外副作用
- 使用废弃的findDOMNode方法
- 检查过时的context API
React过渡动画
期待后续
暂时先空
React中CSS编写
📢 Redux
react在视图层帮助解决了DOM的渲染过程,State依然留给了我们自己来管理 ,react主要负责帮助我们管理视图 state如何维护由开发者决定。
react是帮助我们管理state的容器, Redux则是react中的状态机 当组件需要共享数据时,这时候就可以使用redux进行搭建
js纯函数
- 确定的输入,一定会产生确定的输出
- 函数执行的过程中,不能产生副作用
副作用也称为产生bug的温床
例如 slice 截取数组时不会对原数组进行任何操作,而是生成一个新的数组 splice 截取数组,会返回一个新的数组,也会对原数组进行修改 因此 splice就不是一个纯函数
纯函数的作用和优势
写的时候保证了函数的纯度,只是单纯的实现自己的业务逻辑即可,不需要关心传入的内容是如何获取或者依赖外部变量是否已经发生了修改
使用的时候,确定你的输入内容不会被篡改,并且自己确定的输入,一定会有确定输出
为什么需要redux
Details
管理不断变化的state非常的困难, 因为状态之间相互存在依赖,一个状态改变会引起另一个状态的变化,view页面也可能会引起状态的变化
Redux的核心
Redux中的核心由Store 、action 、reducer构成。
1. Store
store 简而言之,是redux的仓库 用来整合action reducer 从官方的图可值,store由 diaspatcher 、reducer、state构成
- dispatcher是一个函数,用于广播派发action, 将action传给所有的reducer,reducer对其进行处理,然后对state进行更新
- reducer是一个纯函数,用来通过action和state进行更新
- state是一个对象,对数据进行储存
js
// 这里则为Store中 获取数据的方式
store.getState()
// 更新数据
store.dispatch(action)
// store中也可进行订阅数据
const store = require('./store');
const unsubscribe = store.subscribe(()=>{
console.log('订阅数据的变化',store.getState());
})
store.dispatch({ type: 'change_name', name: 'kodnsa' });
store.dispatch({ type: 'add_level', num: 1000 });
unsubscribe();
console.log(store.getState());
2. action
redux要求通过action来更新数据 所有的数据的变化,必须通过派发(dispatch)action来更新
action是一个普通的JS对象,用来描述这次更新的type和content 使用action的好处就是可以清晰的知道数据发生了什么变化,所有的数据变化都是可追踪、可预测的.
3.reducer
- 在React中 reducer也被要求是一个纯函数
- reducer做的事情就是将传入的state和action结合起来生成一个新的state
数据都是存储在store,但是store中的数据来自于reducer的函数。 reducer的函数需要返回state ,这里返回什么样子的state我们store就存储什么样的state。 修改数据通过store中的dispatch派发action 给reducer
reducer把上一次的prevState先传递进去, 根据之前的state和这次的action去 生成新的state.
Redux的三大原则
单一的数据源
- 好处则是 让整个应用程序的state变得方便维护、追踪、修改
state是只读的
使用纯函数来执行修改
redux的搭建使用流程
安装redux
npm install redux --save | yarn add redux
创建一个对象作为要保存的状态
创建Store存储state
- 创建store时必须创建reducer
修改reducer的处理代码
在派发action前,监听store的变化
Redux的文件结构梳理
为了方便后续的统一管理和书写可观性更加的清晰,在创建时对Store、action、reducer、constants进行拆分为一个一个的module
markdown
├─src #其余省略 从src进行开始展示
│ └─store # store文件夹
│ └─actionCreator.js # 帮助创建action
│ └─constant.js # 常量规范书写
│ └─reducer.js # 处理
│ └─index.js # 整合reducer actionCreator 创建store
|
-------------------------------------------------------------------------------------
模块化开发 将其抽为单独的文件进行管理
├─src #其余省略 从src进行开始展示
│ └─store # store文件夹
| └─home # home模块文件夹
│ └─actionCreator.js # 帮助创建action
│ └─constant.js # 常量规范书写
│ └─reducer.js # 处理
│ └─index.js # 整合reducer actionCreator 创建store
|
| └─about # about 模块文件夹
│ └─actionCreator.js # 帮助创建action
│ └─constant.js # 常量规范书写
│ └─reducer.js # 处理
│ └─index.js # 整合reducer actionCreator 创建store
|
| └─index.js # 将home 、about 等多个 单独的模块store 进行整合为最终的store
|
---------------------------[redux一键使用 4步骤 ]-------------------------
TIP
💹 沙发安排
将派发的action生成过程放到actionCreators中
将定义的actionCreator函数放到独立文件中,actionCreators.js
actionCreators和reducer函数中使用字符串常量保持一致, 将其抽离到独立文件constant.js
将reducer和默认值(initialState)放到独立的reducer.js中
下面为 actionCreator
js
const { ADD_LEVEL, CHANGE_NAME } = require('./constant');
const changeAction = (name) => ({
type: CHANGE_NAME,
name
})
const addAction = (level) => ({
type: ADD_LEVEL,
level
})
module.exports = {
changeAction,
addAction
}
下面为各分段文件的内容 constant.js文件
js
const ADD_LEVEL = 'add_level';
const CHANGE_NAME = 'change_name';
module.exports = {
ADD_LEVEL,
CHANGE_NAME
}
reducer.js文件
js
const { ADD_LEVEL, CHANGE_NAME } = require('./constant');
const initialState = {
name: "xiaotian",
level: 22
};
function reducer(state = initialState, aciton) {
console.log('这里是老数据', initialState);
switch (aciton.type) {
case CHANGE_NAME:
return { ...state, name: aciton.name }
case ADD_LEVEL:
return { ...state, level: state.level + aciton.level }
default:
return state
}
}
export default reducer
index.js文件
js
import { createStore } from "redux";
import reducer from "./reducer";
// 创建store
const store = createStore(reduncer);
export default store;
base redux在页面中使用流程
-1.主动引入store -2.将store中的值给页面的初始化值 -3.在didMount/useEffect中订阅当前的状态 -4.取消订阅
若 发生一个事件的时候 需要自己去拿到store 自己去调用dispatch 传入action对象 使其最终更新store
页面App.js中使用
js
import About from './pages/About'
import store from './store'
import React, { memo, useState, useEffect } from 'react'
const App = memo(() => {
const [couter, setCouter] = useState(store.getState().couter);
useEffect(() => {
const unsubscibe = store.subscribe(() => {
const state = store.getState().couter;
setCouter(state)
})
return () => {
unsubscibe();
}
}, [])
return (
<div>
<h2>App: {couter}</h2>
<About />
</div>
</div>
)
})
export default App
About.js中使用
js
import React, { memo, useEffect, useState } from 'react'
import store from '../store'
import { addAction, subAction } from '../store/actionCreator';
const About = memo(() => {
const [couter, setCouter] = useState(store.getState().couter);
useEffect(() => {
const unsubscibe = store.subscribe(() => {
const state = store.getState().couter;
setCouter(state)
})
return () => {
unsubscibe();
}
}, [])
function changenumber(num, isTrue) {
if (isTrue) {
// 派发acition addAction过程在actionCreator已经创建了
store.dispatch(addAction(num))
} else {
store.dispatch(subAction(num))
}
}
return (
<div>
<h2>About ---- {couter}</h2>
<button onClick={e => changenumber(1, true)}>+1</button>
<button onClick={e => changenumber(10, false)}>-10</button>
</div>
)
})
export default About
Redux 图解
📂 React-Redux
React中进行state状态管理的JS库
React-Redux中最核心分别为Provider, connect , useSelector , useDispatch
安装
npm install react-redux | yarn add react-redux
在入口文件中导入Provider 传入store
详细代码说明如下
index.js入口文件设置
js
import ReactDOM from 'react-dom/client';
import App from './App';
// 1. 引入 Provider 、store
import { Provider } from 'react-redux';
// 2.Provider包裹住App 并注入store
import store from './store';
// 底层则为 Context.Provider
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
App.js
js
import React, { PureComponent } from 'react'
import Home from './pages/home'
import './style.css'
import store from './store'
import About from './pages/About.jsx'
export class App extends PureComponent {
constructor() {
super()
this.state = { couter: store.getState().couterInfo.couter }
}
componentDidMount() {
store.subscribe(() => {
const state = store.getState().couterInfo
this.setState({ couter: state.couter.couter })
})
}
render() {
const { couter } = this.state
return (
<div>
<h2>App couter:{couter}</h2>
<div className='pages'>
<Home />
<About />
</div>
</div>
)
}
}
export default App
小黑板 connect
connect函数返回值为一个高阶组件
- 接收2个参数 (mapStateToProps , mapDispatchToProps)mapStateToProps ----映射数据 | mapDispatchToProps----映射dispatch到props
页面组件中使用则 不再像base Redux一样繁琐,只需要引入高阶组件connect ,传入connect所需要的map映射state | dispatch 的数据去给组件即可完成操作 本来是像base Redux一样 通过store.dispatch去操作, 现在不需要去引入store,只需通过props去拿到函数执行 将具体的操作移到了 组件的逻辑操作中了 ====> 相当于做了解耦
React-Redux中 类组件 VS 函数式组件 的区别
类组件中 使用 高阶组件connect(mapStateToProps, mapDispatchToProps)去完成
函数中通过useSelector , useDispatch 接口完成
Home.jsx类组件中使用如下
js
import React, { PureComponent } from 'react'
import { connect } from 'react-redux';
import { addAction, subAction } from '../store/actionCreator.js'
export class Home extends PureComponent {
calcNumber(num, isAdd) {
if (isAdd) {
this.props.addNumber(num);
} else {
this.props.subNumber(num);
}
}
render() {
const { couter } = this.props;
return (
<div>
<h2>Home ---- {couter}</h2>
<button onClick={e => this.calcNumber(6, true)}>+6</button>
<button onClick={e => this.calcNumber(6, false)}>-6</button>
</div>
)
}
}
// connect 的返回值是一个高阶组件 高阶函数接受另一个组件为参数
// connect()接受2个参数 将store中的需要的数据映射到Home组件中
// 目的是为了做映射
const mapStateToProps = (state) => ({
couter: state.couter
})
const mapDispatchToProps = (dispatch) => ({
addNumber(num) {
dispatch(addAction(num))
},
subNumber(num) {
dispatch(subAction(num))
// 可理解为 传入state将其映射到对象中 对象中的函数可以直接被porps调用
}
})
export default connect(mapStateToProps, mapDispatchToProps)(Home)
函数式组件中如下
js
import React, { memo } from 'react'
import { useSelector, useDispatch } from 'react-redux';
import { addAction, subAction } from '../store/actionCreator';
const Home = memo(() => {
const couter = useSelector(state => state.couter);
const dispatch = useDispatch();
const calcNumber = (num, isAdd) => {
if (isAdd) {
dispatch(addAction(num));
} else {
dispatch(subAction(num));
}
}
return (
<div>
<h2>Home ---- {couter}</h2>
<button onClick={e => calcNumber(6, true)}>+6</button>
<button onClick={e => calcNumber(6, false)}>-6</button>
</div>
)
})
export default Home
Redux里组件中的异步操作
前因: 一般在类组件中网络请求的异步代码放到组件的生命周期中完成 ,但事实上,网络请求到的数据也是我们状态管理的一部分,更好的方式则将其交给redux管理.
在类组件中通常在componentDidMount中发送请求 ,通过action将数据保存到store store/reducer.js
js
import * as actionTypes from './constant';
const initialState = {
couter: 110,
banners: [],
recommends: []
}
function reducer(state = initialState, action) {
switch (action.type) {
case actionTypes.ADD_NUMBER:
return { ...state, couter: state.couter + action.num }
case actionTypes.SUB_NUMBER:
return { ...state, couter: state.couter - action.num }
case actionTypes.CHANGE_BANNERS:
return { ...state, banners: action.banners }
case actionTypes.CHANGE_RECOMMEDNS:
return { ...state, recommends: action.recommends }
default:
return state
}
}
export default reducer
组件中的didMount中发送网络请求获取数据 (old version)
js
import React, { PureComponent } from 'react'
import axios from 'axios';
import { connect } from 'react-redux';
import { changeBannersAction, changeRemmendsAction } from '../store/actionCreator'
export class Category extends PureComponent {
componentDidMount() {
axios.get('http://123.207.32.32:8000/home/multidata').then(res => {
const banners = res.data.data.banner.list;
const recommends = res.data.data.recommend.list;
this.props.changeBanners(banners);
this.props.changeRecommends(recommends);
})
}
render() {
return (
<div>Category get Data</div>
)
}
}
const mapDispatchToProps = (dispatch) => ({
changeBanners(banners) {
dispatch(changeBannersAction(banners))
},
changeRecommends(recommends) {
dispatch(changeRemmendsAction(recommends))
}
})
export default connect(null, mapDispatchToProps)(Category)
想要将其 在redux中管理 因此
js
// #上述代码相同部分省略
.........
componentDidMount() {
this.props.fetchHomeData()
}
const mapStateToProps = (state) => ({
counter: state.couter
})
const mapDispatchToProps = (dispatch) => ({
fetchHomeData() {
// 这里将异步获取数据操作放至 函数中
dispatch(fetchHomeMultidation())
}
})
export default connect(mapStateToProps, mapDispatchToProps)(Category)
// dispatchEvent({type:'xxx',payload:{}}) 属于同步操作 在这里就不能进行异步的事情
// 如果要做异步的事情 则需要在redux中进行
产生问题 ——————>
/** *
- 如果是普通的action , 那么我们这里需要返回action对象
- 问题?? 对象中是不能直接拿到服务器请求的异步数据
- return {} is x
- return function √ */
js
export const fetchHomeMultidation = () => {
return (dispatch, getState) => {
axios.get('http://123.207.32.32:8000/home/multidata').then(res => {
const banners = res.data.data.banner.list
const recommends = res.data.data.recommend.list
dispatch(changeBannersAction(banners))
dispatch(changeRemmendsAction(recommends))
})
}
}
返回函数报错提醒---->You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions.
中间件(Middleware)
如何将异步请求交给redux ?
- 普通的action返回的是一个对象
{tpye:ADDNUM,num:123}
- 对象中是无法直接拿到服务器请求的异步操作数据, 若返回结果为函数时该怎么解决?
- 普通action不能返回函数,需要借助中间件来增强,
redux-thunk
,让它支持返回一个函数
目的: 在dispatch的action和最终达到的reducer之间扩展(增强)自己的代码
redux-thunk如何发送异步请求的?
redux-thunk 使得dispatch(action函数) action为一个function 该函数会被调用,并且传给该函数dispatch 、getState 函数
- dispatch函数 派发action
- getState函数 获取之前的状态
redux-thunk
安装
yarn add redux-thunk | npm install redux-thunk
在store中应用Middleware
在使用的时候 定义一个函数的 action
接下来 如代码所述
js
// 步骤1 、2 所示 安装 并且在store中进行应用
// 引入applyMiddleware
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
import thunk from 'redux-thunk';
import homeReducer from './home';
import couterReducer from './couter';
// 导入其余的store信息
import userRedcuer from './user';
// combineReducers实现原理
// function reducer(state={},action){
// return {
// couter:couterReducer(state.couter,action),
// home:homeReducer(state.home,action),
// user:userRedcuer(state.user,action)
// }
// }
const reducer = combineReducers({
couterInfo: couterReducer,
homeInfo: homeReducer,
code: userRedcuer
})
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
// 在使用工具时 为了通过 ----Redux DevTools ---查看state需要设置 (生产环境时记得删除掉)
// 使用composeEnhancers 包裹住 applyMiddleware(thunk)
const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)));
export default store
🔍 小黑板 combineReducers
combineReducers 目的是为了合并 通过调用reducer函数来返回一个新的对象 将多个reducer合并
如何实现???
将传入的reducer合并为一个对象,最终返回一个combination函数,在执行该函数过程中,它会通过判断前后返回数据是否相同 --->决定返回 旧state | 新 state
新的state会触发订阅者发生对于的刷新,而旧的state可以有效的组织订阅者发生刷新
在组件中使用界面
js
import React, { PureComponent } from 'react'
import { connect } from 'react-redux';
import { fetchHomeMultidation } from '../store/home'
export class Category extends PureComponent {
componentDidMount() {
this.props.fetchHomeData()
}
render() {
return (
<div>Category get Data {this.props.counter}</div>
)
}
}
const mapStateToProps = (state) => ({counter: state.couterInfo.couter})
const mapDispatchToProps = (dispatch) => ({
fetchHomeData() {
// 这里将 异步操作 封装在fetchHomeMultidation中
dispatch(fetchHomeMultidation())
}
})
export default connect(mapStateToProps, mapDispatchToProps)(Category)
// dispatchEvent({type:'xxx',payload:{}}) 属于同步操作 在这里就不能进行异步的事情
// 如果要做异步的事情 则需要在redux中进行
在store/home/actionCreator.js中定义 返回的action为函数 (这里同上产生问题处一致 )
js
// 步骤3
export const fetchHomeMultidation = () => {
return (dispatch, getState) => {
axios.get('https:tinaer.cn:322/youco').then(res => {
const banners = res.data.data.banner.list
const recommends = res.data.data.recommend.list
console.log(banners);
dispatch(changeBannersAction(banners))
dispatch(changeRemmendsAction(recommends))
})
}
}
📍 ReduxToolkit
官方推荐编写的Redux逻辑方法 简称为'RTK'
- 安装
npm install @reduxjs/toolkit react-redux
Redux Toolkit的核心API主要分为如下
- configureStore
自动组合slice reducer添加提供的任何Redux中间件 redux-thunk默认包含,并启用Redux DevTools Extension
- createSlice
接受reducer函数的对象、切片名称和初始值,并自动生成切片reducer并带有相应的actions
- createAsyncThunk
接受一个动作类型的字符串和一个返回承诺的函数,并生成一个pending/fulfilled/rejected基于该承诺分派动作类型的thunk
重构代码 - 创作counter的reducer
js
import { createSlice } from '@reduxjs/toolkit';
const couterSlice = createSlice({
name: 'couter',
initialState: {
couter: 777
},
reducers: {
addNumber(state, { payload }) {
state.couter += payload
},
subNumber(start, { payload }) {
start.couter -= payload
}
}
})
export const { addNumber, subNumber } = couterSlice.actions;
export default couterSlice.reducer
store创建
js
import { configureStore } from '@reduxjs/toolkit';
import couterReducer from './features/couter'
import HomeReducer from './features/home'
const store = configureStore({
reducer: {
couter: couterReducer,
home: HomeReducer
}
})
export default store
Redux Toolkit异步操作
redux-thunk中间件让dispatch中可以进行异步操作
js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import axios from 'axios'
export const getHomeMultidataAction = createAsyncThunk(
"fetch/homemultidata",
async (extraInfo, { dispatch, getState }) => {
// console.log(extraInfo, dispatch, getState)
// 1.发送网络请求, 获取数据
const res = await axios.get("http://123.207.32.32:8000/home/multidata")
// 2.取出数据, 并且在此处直接dispatch操作(可以不做)
const banners = res.data.data.banner.list
const recommends = res.data.data.recommend.list
dispatch(getBanners(banners))
dispatch(getRecommends(recommends))
// 3.返回结果, 那么action状态会变成fulfilled状态
return res.data
})
const homeSlice = createSlice({
name: "home",
initialState: {
banners: [],
recommends: []
},
reducers: {
getBanners(state, { payload }) {
state.banners = payload
},
getRecommends(state, { payload }) {
state.recommends = payload
}
},
// extraReducers: {
// [fetchHomeMultidataAction.pending](state, action) {
// console.log("fetchHomeMultidataAction pending")
// },
// [fetchHomeMultidataAction.fulfilled](state, { payload }) {
// state.banners = payload.data.banner.list
// state.recommends = payload.data.recommend.list
// },
// [fetchHomeMultidataAction.rejected](state, action) {
// console.log("fetchHomeMultidataAction rejected")
// }
// }
})
export const { getBanners, getRecommends } = homeSlice.actions
export default homeSlice.reducer
extraReducer可以传入一个函数 函数接收一个builder参数 通过case来监听异步操作的结果
js
extraReducers: (builder) => {
// builder.addCase(fetchHomeMultidataAction.pending, (state, action) => {
// console.log("fetchHomeMultidataAction pending")
// }).addCase(fetchHomeMultidataAction.fulfilled, (state, { payload }) => {
// state.banners = payload.data.banner.list
// state.recommends = payload.data.recommend.list
// })
}