1、setState
(1). setState(stateChange, [callback])------对象式的setState
1.stateChange为状态改变对象(该对象可以体现出状态的更改)
2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
(2). setState(updater, [callback])------函数式的setState
1.updater为返回stateChange对象的函数。
2.updater可以接收到state和props。
4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
总结:
1.对象式的setState是函数式的setState的简写方式(语法糖)
2.使用原则:
(1).如果新状态不依赖于原状态 ===> 使用对象方式
(2).如果新状态依赖于原状态 ===> 使用函数方式
(3).如果需要在setState()执行后获取最新的状态数据,
要在第二个callback函数中读取
写法一:对象式
import React, { Component } from 'react'
export default class Demo extends Component {
state = {count:0}
addCount = () => {
// 获取原来的状态值
const {count} = this.state
// 更新状态值
this.setState({count:count + 1},()=>{
console.log('callback里面的状态',this.state.count)
})
// 监测查看状态值
console.log('callback外面的状态',this.state.count)
}
render() {
return (
<div>
<h1>当前求和为:{this.state.count}</h1>
<button onClick={this.addCount}>点我+1</button>
</div>
)
}
}
以上代码,就是一个简单的数字相加的功能,当我点击按钮【点我+1】的时候,状态值自增1。但是这里我们需要关注的是查看调用setState()
方法后,在其callback
内部与外部打印的状态值是否一样。
这里我们看到更新后打印的状态值是不一样的,在调用setState
方法后,组件的状态并不会立即改变,而是在下一次渲染时才会更新。如果你在setState
方法调用后立即打印组件的状态,你会发现它并没有发生变化。这是因为setState
方法只是向React内部发出了一个更新组件状态的请求,而实际的更新过程是在下一次渲染时才会进行的。如果你需要及时获取组件的最新状态,你可以在组件的shouldComponentUpdate
方法中进行判断,并在需要的时候调用setState
方法进行更新。
写法二:函数式
import React, { Component } from 'react'
export default class Demo extends Component {
state = {count:0}
addCount = () => {
// 获取原来的状态值
const {count} = this.state
// 更新状态值
this.setState((state,props)=>({count:count + 1}),()=>{
console.log('callback里面的状态',this.state.count)
})
// 监测查看状态值
console.log('callback外面的状态',this.state.count)
}
render() {
return (
<div>
<h1>当前求和为:{this.state.count}</h1>
<button onClick={this.addCount}>点我+1</button>
</div>
)
}
}
函数式就是第一个参数传递的是一个函数,它可以接收两个参数(state
,props
),返回值是一个状态对象。
2、LazyLoad
路由组件的懒加载
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Login = lazy(()=>import('@/pages/Login'))
//2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
<Suspense fallback={<h1>loading.....</h1>}>
<Switch>
<Route path="/xxx" component={Xxxx}/>
<Redirect to="/login"/>
</Switch>
</Suspense>
案例代码
import React, { Component, lazy, Suspense } from 'react'
import { NavLink, Route, Routes } from 'react-router-dom'
const Home = lazy(() => import('./Home'))
const About = lazy(() => import('./About'))
export default class Demo extends Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header">
<h2>React Router Demo</h2>
</div>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
<NavLink className="list-group-item" to="/home">
Home
</NavLink>
<NavLink className="list-group-item" to="/about">
About
</NavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
<Suspense fallback={<h1>Loading...</h1>}>
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</div>
</div>
</div>
</div>
</div>
)
}
}
这里懒加载引入组件,必须配合Suspense
使用,不然会报错。
原来普通引入的组件,加载页面的时候会把所有组件一次性加载完,现在使用懒加载后,当你用到该组件的时候才会去动态引入。
这种做法有以下几个好处:
- 提高网页加载速度:如果网页中的图片、
CSS
、JavaScript
等资源都是懒加载的,那么当用户第一次访问网页时,只会加载必要的资源,而不是全部资源。这样就可以大大减少页面加载时间,提高用户体验。 - 减少页面内存占用:如果网页中的资源全部都是懒加载的,那么在页面加载时就不会一次性加载完所有资源,这样就可以避免内存不足的问题。
- 提高网站安全性:懒加载可以减少网站被黑客攻击的风险。因为懒加载可以避免在页面加载时全部资源都加载到浏览器缓存中,这样就可以避免攻击者利用缓存中的资源来攻击网站。
- 方便后续维护:如果网页中的资源是懒加载的,那么在后续维护时就可以更方便地修改或删除资源。这样可以减少对网站的影响,提高维护效率。
3、State Hook ===> React.useState()
(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue)
(3). useState()说明:
参数: 第一次初始化指定的值在内部作缓存
返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
(4). setXxx()2种写法:
setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
案例代码:
import React from 'react'
export default function Demo () {
const [count,setCount] = React.useState(0)
function addCount() {
setCount(count + 1)
}
return (
<div>
<h1>当前求和为:{count}</h1>
<button onClick={addCount}>点我+1</button>
</div>
)
同样是一个点击加1的功能组件,只不过由类式组件改成了函数式组件。
关键代码:
const [count,setCount] = React.useState(0)
count
: 内部当前的状态值setCount
: 操作该状态的方法
其中setCount
有两种写法:
setCount( count + 1)
, 参数直接是一个值setCount( oldCount => oldCount + 1)
,参数是一个函数,内部可以介绍旧的状态,返回一个新的状态
4、Effect Hook ===> React.useEffect()
(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
(2). React中的副作用操作:
发ajax请求数据获取
设置订阅 / 启动定时器
手动更改真实DOM
(3). 语法和说明:
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
(4). 可以把 useEffect Hook 看做如下三个函数的组合
componentDidMount()
componentDidUpdate()
componentWillUnmount()
案例代码:
import React from 'react'
import root from '../../index'
export default function Demo () {
const [count,setCount] = React.useState(0)
React.useEffect(()=>{
let timer = setInterval(() => {
console.log('@@@')
setCount( count + 1)
}, 1000);
return () => {
clearInterval(timer)
}
})
function addCount() {
setCount(count + 1)
}
function unmount () {
root.unmount()
}
return (
<div>
<h1>当前求和为:{count}</h1>
<button onClick={addCount}>点我+1</button>
<button onClick={unmount}>点击卸载组件</button>
</div>
)
}
以上代码,打开页面就会每隔1秒自增1,新增卸载组件功能。
关键代码1:
import root from '../../index'
//-----------------------------
function unmount () {
root.unmount()
}
在react18.x
版本中,卸载组件需要使用root.unmount()
。
关键代码2:
React.useEffect(()=>{
let timer = setInterval(() => {
console.log('@@@')
setCount( count + 1)
}, 1000);
return () => {
clearInterval(timer)
}
})
在副作用函数中,开启了一个定时器,在返回的函数中清除了定时器。
使用useEffect
模拟componentDidMount
import React, { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// 在这里执行您的副作用操作,例如 API 调用等
console.log('componentDidMount');
// 如果需要在组件卸载时执行清理操作,请返回一个函数
return () => {
console.log('componentWillUnmount');
};
}, []); // 空数组表示没有依赖项,因此副作用函数仅在组件挂载时运行
return <div>MyComponent</div>;
}
要在函数组件中模拟 componentDidMount
,您可以使用 useEffect
并传递一个空数组 []
作为依赖项。这将确保副作用函数仅在组件挂载时运行一次。
使用useEffect
模拟componentDidUpdate
import React, { useEffect } from 'react';
function MyComponent({ someProp }) {
useEffect(() => {
// 在这里执行您的副作用操作,例如根据 prop 更改更新数据等
console.log('componentDidUpdate');
// 如果需要在组件卸载时执行清理操作,请返回一个函数
return () => {
console.log('componentWillUnmount');
};
}, [someProp]); // 将需要观察的变量添加到依赖项数组中
return <div>MyComponent</div>;
}
要模拟 componentDidUpdate
,您可以使用 useEffect
,并将需要观察的变量添加到依赖项数组中。这将确保副作用函数在依赖项更改时运行。
使用useEffect
模拟componentWillUnmount
React.useEffect(()=>{
// 开启一个定时器
let timer = setInterval(()=>{
console.log('开启副作用')
},1000)
// 这里return的函数,可以当做componentWillUnmount,在这里清除副作用
return () => {
clearInterval(timer)
console.log('清除副作用')
}
},[])
要模拟 componentWillUnmount
,您可以在 useEffect
的副作用函数中返回一个函数。这个返回的函数将在组件卸载时执行。
5、Ref Hook ===> React.useRef()
(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = useRef()
(3). 作用:保存标签对象,功能与React.createRef()一样
在 React
中,useRef
是一个非常有用的 Hook
,它可以帮助您在函数组件中创建和访问可变的引用。useRef
返回一个可变的 ref
对象,其 .current
属性被初始化为传递的参数(默认为 null
)。这个ref
对象在组件的整个生命周期内保持不变。
以下是如何在 React
中使用 useRef
:
创建一个 ref
import React, { useRef } from 'react';
function MyComponent() {
const myRef = useRef(null);
// ...
}
使用 ref
您可以使用创建的 ref
来引用 DOM
元素或其他 React
组件。要将 ref
附加到 DOM
元素,只需将 ref
传递给元素的 ref
属性。
import React, { useRef } from 'react';
function MyComponent() {
const myRef = useRef(null);
return (
<div>
<input ref={myRef} />
</div>
);
}
现在,myRef.current
将指向输入元素。
访问 ref 的值
要访问 ref
的值,您可以使用 .current
属性。例如,您可以在事件处理程序中访问输入元素的值。
import React, { useRef } from 'react';
function MyComponent() {
const myRef = useRef(null);
const handleClick = () => {
console.log('Input value:', myRef.current.value);
};
return (
<div>
<input ref={myRef} />
<button onClick={handleClick}>Log input value</button>
</div>
);
}
在这个示例中,当用户点击按钮时,输入元素的值将被记录到控制台。
通过这些示例,您可以了解如何在 React
中使用 useRef
。请注意,useRef
不仅可以用于访问 DOM
元素,还可以用于存储任何可变值,而不会触发组件重新渲染 。
6、Fragment 和空标签(<> 和 </>)
在 React
中,Fragment
和空标签(<>
和 </>
)都用于组合多个子元素,而不需要添加额外的 DOM
元素。这在避免不必要的 DOM
层次结构和提高性能方面非常有用。
使用 Fragment
要使用 Fragment
,您需要从 react
包中导入它,然后将它作为包裹元素。以下是一个示例:
import React, { Fragment } from 'react';
function MyComponent() {
return (
<Fragment>
<div>Element 1</div>
<div>Element 2</div>
</Fragment>
);
}
在这个示例中,Element 1
和 Element 2
将作为同级元素渲染,而不需要额外的包裹元素。
使用空标签
空标签(<>
和 </>
)是 Fragment
的简写语法。它们的功能与 Fragment
相同,但更简洁。以下是一个使用空标签的示例:
import React from 'react';
function MyComponent() {
return (
<>
<div>Element 1</div>
<div>Element 2</div>
</>
);
}
在这个示例中,Element 1
和 Element 2
也将作为同级元素渲染,而不需要额外的包裹元素。
请注意,虽然空标签更简洁,但它们不支持添加 key
属性。如果您需要在迭代中使用 Fragment
并添加 key
属性,您应该使用完整的 <Fragment>
标签。
7、Context
在 React
中,Context
是一种在组件树中传递数据的方法,而无需通过每个中间组件显式传递 props
。这在处理跨越多个层次的全局数据(如主题、语言设置或用户信息)时非常有用。
(1)、创建 Context
首先,您需要创建一个 Context
。通常,您会在应用程序的顶层创建一个 Context
。
import React from 'react';
const MyContext = React.createContext();
export default MyContext
(2)、使用Provider传递Context值
要在组件树中提供 Context
值,您需要使用 Provider
组件。Provider
接受一个 value
属性,该属性将作为 Context
的值传递给所有使用该 Context
的子组件。
import React, { Component } from 'react'
import B from './B'
import MyContext from './MyContext'
const {Provider} = MyContext
export default class Demo extends Component {
render() {
return (
<div>
<Provider value={{username:'tom',age:18}}>
<B/>
</Provider>
</div>
)
}
}
(3)、在类式组件中使用static contextType接收Context值
在类组件中访问 Context
的方法是使用 static contextType
。将 contextType
设置为您要访问的 Context
,然后在组件中,您可以通过 this.context
访问 Context
的值。
import React, { Component } from 'react'
import MyContext from '../MyContext'
import C from '../C'
import D from '../D'
export default class B extends Component {
static contextType = MyContext
render() {
console.log('我是B组件接收的context',this.context)
return (
<div>
<h2>我是B组件{this.context.username+','+this.context.age}</h2>
<C/>
<D/>
</div>
)
}
}
(4)、使用Customer在函数组件和类组件中接收Context值
在类组件和函数组件中,您可以使用 Context.Consumer
组件来访问 Context 值。Context.Consumer
接受一个函数作为子元素,该函数接收 Context
的值作为参数。
import MyContext from "../MyContext"
const {Consumer} = MyContext
export default function C () {
return (
<div>
<Consumer>
{value => {
console.log('我是C组件接收的Context',value)
return <h3>我是C组件{value.username+''+value.age}</h3>
}}
</Consumer>
</div>
)
}
(5)、使用useContext Hook在函数组件中接收Context值
要在组件中使用 Context
值,您可以使用 useContext Hook
。useContext
接受一个 Context
对象,并返回该 Context
的当前值。
import { useContext } from 'react'
import MyContext from '../MyContext'
export default function D () {
const contextValue = useContext(MyContext)
console.log('我是D组件接收的context',contextValue)
return (
<div>
<h3>我是D组件</h3>
<div>{contextValue.username+','+contextValue.age}</div>
</div>
)
}
请注意,虽然 Context
可以简化跨多个层次的数据传递,但在不需要的情况下过度使用 Context
可能会导致组件之间的不必要的耦合。因此,建议仅在确实需要全局数据时使用 Context
,在应用开发中一般不用Context
, 一般都用它的封装react
插件
8、组件优化
Component的2个问题
只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低
只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
效率高的做法
只有当组件的state或props数据发生改变时才重新render()
原因
Component中的shouldComponentUpdate()总是返回true
解决
办法1:
重写shouldComponentUpdate()方法
比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
办法2:
使用PureComponent
PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
注意:
只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
不要直接修改state数据, 而是要产生新数据
项目中一般使用PureComponent来优化
如何理解pureComponent
我们首先需要了解React
组件的基本概念。在React
中,有两种类型的组件:函数组件和类组件。PureComponent
是类组件的一种特殊类型,它可以帮助我们优化性能。
PureComponent
是React
提供的一个基类,它继承自React.Component
。与普通的Component
不同,PureComponent
实现了一个浅比较(shallow comparison
)的shouldComponentUpdate
方法。这意味着,当组件的props
或state
发生变化时,PureComponent
会对新旧props
和state
进行浅比较,如果它们相等,那么组件就不会重新渲染。这可以避免不必要的渲染,从而提高性能。
要使用PureComponent
,只需将组件的基类从React.Component
更改为React.PureComponent
。例如:
import React from 'react';
class MyComponent extends React.PureComponent {
// ...
}
需要注意的是,PureComponent
的浅比较可能会导致一些问题。例如,当组件的props
或state
包含嵌套对象时,浅比较可能无法检测到嵌套对象的变化。因此,在使用PureComponent
时,需要确保组件的props
和state
是不可变的(immutable
)。
总之,PureComponent
是一种特殊的React类组件,它通过实现浅比较的shouldComponentUpdate
方法来避免不必要的渲染,从而提高性能。在使用PureComponent
时,需要确保组件的props
和state
是不可变的
使用shouldComponentUpdate进行自定义优化
如果PureComponent
或React.memo
的浅比较不足以满足需求,可以在类组件中实现自定义的shouldComponentUpdate
方法。这允许您根据特定条件决定组件是否应该重新渲染。例如:
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// 自定义比较逻辑
return nextProps.someProp !== this.props.someProp;
}
// ...
}
React.memo
是React
库中的一个高阶组件(Higher-Order Component,简称HOC),它用于优化函数组件的性能。React.memo
的作用类似于类组件中的PureComponent
,它通过对组件的props进行浅比较(shallow comparison
)来避免不必要的重新渲染。当使用
React.memo
包装一个函数组件时,只有当组件的props
发生变化时,组件才会重新渲染。这可以帮助我们减少不必要的渲染,从而提高性能。例如:import React from 'react'; const MyComponent = React.memo(function MyComponent(props) { // ... });
需要注意的是,
React.memo
的浅比较可能会导致一些问题。例如,当组件的props
包含嵌套对象时,浅比较可能无法检测到嵌套对象的变化
9、render props
如何向组件内部动态传入带内容的结构(标签)?
Vue中:
使用slot技术, 也就是通过组件标签体传入结构 <A><B/></A>
React中:
使用children props: 通过组件标签体传入结构
使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
children props
<A>
<B>xxxx</B>
</A>
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到
标签体是一种React
模式,它允许您将一个或多个React
元素作为组件的子元素传递,并在组件中渲染这些元素。这些子元素可以是任何有效的React
元素,包括其他组件。例如:
function MyComponent(props) {
return (
<div>
{props.children}
</div>
);
}
function App() {
return (
<MyComponent>
<div>Child 1</div>
<div>Child 2</div>
</MyComponent>
);
}
在这个例子中,MyComponent
接受一个名为children
的prop
,该prop
是一个React
元素或一组React
元素。在MyComponent
中,我们使用props.children
来渲染这些子元素。在App
组件中,我们将两个<div>
元素作为MyComponent
的子元素传递。这些元素将被渲染为MyComponent
的子元素。
render props
<A render={(data) => <C data={data}></C>}></A>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data}
render props
是另一种React
模式,它允许您将一个函数作为prop
传递给组件,并在组件中调用该函数以渲染子元素。这个函数通常称为render
或children
。例如:
function MyComponent(props) {
const { render } = props;
const data = { /* some data */ };
return (
<div>
{render(data)}
</div>
);
}
function App() {
return (
<MyComponent
render={(data) => (
<div>
{/* render something based on the data */}
</div>
)}
/>
);
}
在这个例子中,MyComponent
接受一个名为render
的prop
,该prop
是一个函数。在MyComponent
中,我们调用render
函数并将数据作为参数传递。render
函数返回一个React
元素,该元素用于渲染MyComponent
的子元素。
总之,标签体和render props
是两种不同的React
模式,它们的作用和用法也有所不同。标签体用于将React
元素作为组件的子元素传递,而render props
用于将函数作为prop
传递,并在组件中调用该函数以渲染子元素。这两种模式都可以用于实现组件的可重用性和灵活性
10、 错误边界
理解:
错误边界(Error boundary
):用来捕获后代组件错误,渲染出备用页面
特点:
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误。
使用方式:getDerivedStateFromError
配合componentDidCatch
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
// 在render之前触发
// 返回新的state
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// 统计页面的错误。发送请求发送到后台去
console.log(error, info);
}
对于React
应用程序中的错误处理,可以使用错误边界来捕获和处理组件中的错误。错误边界是一种React
组件,它可以捕获其子组件中的错误,并显示备用UI
而不是崩溃的UI
。以下是一个简单的错误边界组件的示例:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
console.error(error, errorInfo);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>服务器异常,请稍后在试.</h1>;
}
return this.props.children;
}
}
在这个例子中,ErrorBoundary
是一个错误边界组件。它通过getDerivedStateFromError
和componentDidCatch
方法来捕获子组件中的错误,并在发生错误时显示备用UI
。getDerivedStateFromError
方法返回一个对象,该对象将更新组件的状态以显示备用UI
。componentDidCatch
方法可以用于记录错误或将错误发送到错误报告服务。
要使用错误边界,只需将其包装在可能会发生错误的组件周围即可。例如:
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
在这个例子中,MyComponent
是可能会发生错误的组件。通过将MyComponent
包装在ErrorBoundary
中,我们可以捕获并处理MyComponent
中的错误。
案例:我们在render
里面写一段错误代码,render
属于生命周期
import React, { Component } from 'react'
export default class Demo extends Component {
state = {users:'users'}
render() {
return (
<div>
我是Demo组件
{
this.state.users.map((user)=>{
return <h1>{user.name}</h1>
})
}
</div>
)
}
}
查看效果:
11、组件通信方式总结
组件间的关系:
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
几种通信方式:
1.props:
(1).children props
(2).render props
2.消息订阅-发布:
pubs-sub、event等等
3.集中式管理:
redux、dva等等
4.conText:
生产者-消费者模式
比较好的搭配方式:
父子组件:props
兄弟组件:消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)