案例:
export default class Demo extends Component {
state = {
count: 0
}
add = () => {
// 获取当前的值
const { count } = this.state
// 更新状态
this.setState({ count: count + 1 })
console.log(count);
}
render() {
const { count } = this.state
return (
当前求和为:{count}
)
}
}
可以看到
setState
更新视图是一个异步的动作,同步的输出事件只能获取更新前的状态。可知setState()
是立即执行同步的,但是它引起的后续动作(reac
t模版更新)是异步的,要等后续的进程执行完再更新视图。
setStata
函数可以传入两个参数,除了需要更新的state
状态对象,还可以传入一个回调函数,这个回调函数是一个异步函数
export default class Demo extends Component {
state = {
count: 0
}
add = () => {
// 获取当前的值
const { count } = this.state
// 更新状态
this.setState({ count: count + 1 },() => {
console.log(this.state.count)
})
}
render() {
const { count } = this.state
return (
当前求和为:{count}
)
}
}
setState(stateChange, [callback])
stateChange
为状态改变对象(该对象可以体现出状态的更改)
this.setState({ count: count + 1 },() => {})
callback
是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
this.setState({ count: count + 1 },() => {
console.log(this.state.count)
})
setState(updater, [callback])
updater
为返回stateChange
对象的函数。
this.setState(() => {
return {count: this.state.count + 1}
},() => {})
updater
可以接收到state
和props
。
this.setState((state,props) => {
console.log(state,props);
return {count: state.count + 1}
},() => {})
callback
是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
this.setState((state,props) => {
return {count: state.count + 1}
},() => {
console.log(state.count)
})
对象式的setState是函数式的setState的简写方式(语法糖)
使用原则:
如果新状态不依赖于原状态 ===> 使用对象方式
this.setState({count:99})
如果新状态依赖于原状态 ===> 使用函数方式
this.setState(state => ({ count: state.count + 1 }))
如果需要在setState()
执行后获取最新的状态数据,要在第二个callback
函数中读取
在实际开发中,整个React应用会有许多的组件,一般情况下在运行React项目时,程序会将所有组件全部加载,这样还未用到的组件也被加载,这样会影响程序整体运行的速度
实验:
export default class Demo extends Component {
render() {
return (
路由项目
About
Home
)
}
}
可以看出当点击每个组件的链接Tab时,程序并没有发送加载资源请求,说明一开始运行程序的时候,所有组件已经全部请求完毕了,为了减少资源的浪费和提升程序的性能,需要用到路由的懒加载。
通过React的
lazy
函数配合import()
函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Home=lazy(() => import('./Home'))
const About=lazy(() => import('./About'))
通过
组件包裹需要懒加载的组件,配置
Lazy
使用
export default class Demo extends Component {
render() {
return (
路由项目
About
Home
)
}
}
还可以通过
指定在加载得到路由打包文件前显示一个自定义
loading
界面,当网速慢或者其他原因导致组件加载请求慢时会有一个备用组件作为优先显示
Loading.....}>
(1).
Hook
是React 16.8.0版本增加的新特性/新语法
(2). 可以让你在函数组件中使用state
以及其他的 React 特性
State Hook
让函数组件也可以有state
状态, 并进行状态数据的读写操作
//类式组件
class Demo extends Component {
state = {
count: 0
}
add = () => {
this.setState(state => ({ count: state.count + 1 }))
}
render() {
const { count } = this.state
return (
当前求和为:{count}
)
}
}
语法:
const [xxx, setXxx] = React.useState(initValue)
useState()
说明:
//函数式组件
function Demo() {
const [count, setCount] = React.useState(0)
function add() {
setCount(count+1)
}
return (
当前求和为:{count}
)
}
setXxx()
的2种写法:setXxx(newValue)
: 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue)
: 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
function add() {
// 第一种写法
setCount(count+1)
// 第二种写法
setCount(count => count + 1)
}
Effect Hook
可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
// 类式组件写法
class Demo extends Component {
state = {
count: 0
}
unmount = () => {
createRoot(document.getElementById('root')).unmount();
}
componentDidMount() {
this.timer = setInterval(() => {
this.setState(state => ({ count: state.count + 1 }))
}, 1000)
}
componentWillUnmount() {
clearInterval(this.timer)
}
render() {
const { count } = this.state
return (
)
}
}
React中的副作用操作:
ajax
请求数据获取语法和说明:
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => {}
}, [stateValue])
//函数式组件
function Demo() {
const [count, setCount] = React.useState(0)
React.useEffect(() => {
let timer = setInterval(() => {
setCount(count => count + 1)
}, 1000)
return () => {
clearInterval(timer)
}
}, [])
function unmount() {
createRoot(document.getElementById('root')).unmount();
}
return (
当前求和为:{count}
)
}
分析:
使用React.useEffect()
时不写第二个参数,那么Effect Hook
会监测所有状态值的变化
function Demo() {
const [count, setCount] = React.useState(0)
const [name, setName] = React.useState('Tom')
React.useEffect(() => {
console.log('@@');
})
function add() {
setCount(count => count + 1)
}
function changeName() {
setName('Jack')
}
return (
当前求和为:{count}
当前的名字为{name}
)
}
使用React.useEffect()
时写一个空数组为参数,那么Effect Hook
不会监测任何状态值的变化,回调函数只会在第一次render()后执行
React.useEffect(() => {
console.log('@@');
},[])
使用React.useEffect()
时写值为指定状态值的非空数组作为第二个参数,那么Effect Hook
只会监测指定状态值的变化
React.useEffect(() => {
console.log('@@');
},[name])
React.useEffect()
中的返回函数return
在组件卸载前执行,一般在此做一些收尾工作, 比如清除定时器/取消订阅等,
注意点:每次执行这里的更新该函数也会去执行一遍
React.useEffect(() => {
let timer = setInterval(() => {
setCount(count => count + 1)
}, 1000)
return () => {
clearInterval(timer)
}
},[])
可以把 useEffect Hook 看做如下三个函数的组合
Ref Hook
可以在函数组件中存储/查找组件内的标签或任意其它数据
// 类式组件的写法
class Demo extends Component {
myRef=React.createRef();
show=() => {
alert(this.myRef.current.value)
}
render() {
return (
)
}
}
语法:
const refContainer = useRef()
作用:保存标签对象,功能与React.createRef()
一样
// 函数式组件的写法
function Demo() {
const myRef=React.useRef()
function show() {
alert(myRef.current.value)
}
return (
)
}
<>>
import React, { Component, Fragment } from 'react'
export default class Demo extends Component {
render() {
return (
)
}
}
import React, { Component, Fragment } from 'react'
export default class Demo extends Component {
render() {
return (
<>
>
)
}
}
可以不用必须有一个真实的DOM根标签了
未使用
Fragment
标签:
使用
Fragment
标签:
一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
需求:将A组件状态中的数据传递给C组件,不通过B组件的二次传递
export default class A extends Component {
state = {
username: 'tom',
age: 18
}
render() {
const { username, age } = this.state
return (
我是A组件
我的用户名是:{username}
我的年龄是:{age}
)
}
}
class B extends Component {
render() {
return (
我是B组件
)
}
}
class C extends Component {
render() {
return (
我是C组件
我从A组件拿到的用户名是:???
我从A组件拿到的年龄时是:???
)
}
}
创建Context容器对象:
const XxxContext = React.createContext()
// 创建context对象
const UserNameContext = React.createContext()
渲染子组时,外面包裹xxxContext.Provider
, 通过value
属性给后代组件传递数据:
子组件
const { Provider } = UserNameContext
export default class A extends Component {
state = {
username: 'tom',
age: 18
}
render() {
const { username, age } = this.state
return (
我是A组件
我的用户名是:{username}
我的年龄是:{age}
)
}
}
后代组件读取数据:
第一种方式:仅适用于类组件
static contextType = xxxContext // 声明接收context
this.context // 读取context中的value数据
class C extends Component {
// 声明接收context
static contextType = UserNameContext
render() {
console.log(this);
console.log(this.context);
const {username,age}=this.context
return (
我是C组件
我从A组件拿到的用户名是:{username}
我从A组件拿到的年龄时是:{age}
)
}
}
第二种方式: 函数组件与类组件都可以
{
value => ( // value就是context中的value数据
//要显示的内容
)
}
// 创建context对象
const UserNameContext = React.createContext()
const { Provider, Consumer } = UserNameContext
function C() {
return (
我是C组件
{
value => {
return (
我从A组件拿到的用户名是:{value.username}
我从A组件拿到的年龄时是:{value.age}
)
}
}
)
}
在应用开发中一般不用context, 一般都用它的封装react插件
只要执行setState()
,即使不改变状态数据, 组件也会重新render()
==> 效率低
export default class Parent extends Component {
state = {
carName: '奔驰'
}
changeName = () => {
const { carName } = this.state
this.setState({})
}
render() {
console.log('parent---render');
return (
我是Parent组件
我的车的名字是:{this.state.carName}
)
}
}
只要当前组件重新render()
, 就会自动重新render
子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
export default class Parent extends Component {
state = {
carName: '奔驰',
stus:['小王','小李','小芳']
}
changeName = () => {
const { carName } = this.state
this.setState({})
}
render() {
console.log('parent---render');
return (
我是Parent组件
我的车的名字是:{this.state.carName}
)
}
}
class Child extends Component {
render() {
console.log('child---render');
return (
我是Child组件
我接受到的车的名字是:{this.props.carName}
)
}
}
只有当组件的
state
或props
数据发生改变时才重新render()
Component
中的shouldComponentUpdate()
总是返回true
重写shouldComponentUpdate()
方法
比较新旧state
或props
数据, 如果有变化才返回true
, 如果没有返回false
// person组件
class Parent extends Component {
state = {
carName: '奔驰',
}
changeName = () => {
const { carName } = this.state
this.setState({})
}
shouldComponentUpdate(nextProps,nextState){
return !this.state.carName===nextState.carName
}
render() {
console.log('parent---render');
return (
我是Parent组件
我的车的名字是:{this.state.carName}
)
}
}
// Child组件
class Child extends Component {
shouldComponentUpdate(nextProps,nextState){
return !this.props.carName===nextProps.carName
}
render() {
console.log('child---render');
return (
我是Child组件
我接受到的车的名字是:{this.props.carName}
)
}
}
使用PureComponent
import React, { PureComponent} from 'react'
PureComponent
重写了shouldComponentUpdate()
, 只有state
或props
数据有变化才返回true
export default class Parent extends PureComponent {
state = {
carName: '奔驰',
}
changeName = () => {
const { carName } = this.state
this.setState({})
}
render() {
console.log('parent---render');
return (
我是Parent组件
我的车的名字是:{this.state.carName}
)
}
}
class Child extends PureComponent {
render() {
console.log('child---render');
return (
我是Child组件
我接受到的车的名字是:{this.props.carName}
)
}
}
注意:
只是进行state
和props
数据的浅比较, 如果只是数据对象内部数据变了, 返回false
不要直接修改state
数据, 而是要产生新数据
export default class Parent extends PureComponent {
state = {
carName: '奔驰'
}
changeName = () => {
const obj = this.state
obj.carName = '迈巴赫'
console.log(obj === this.state);
this.setState(obj)
}
render() {
console.log('parent---render');
return (
我是Parent组件
我的车的名字是:{this.state.carName}
)
}
}
PureComponent
的底层做了一个浅对比,不会管Obj
里面的属性是否发生变化,只要Obj
与this.state
是同一个对象,就不会引起更新
在之前更新数组类型的状态时说过不能通过使用
push
、unshift
等原生数组的方法改写数组来更新状态,需要返回一个新的数组进行更新
export default class Parent extends PureComponent {
state = {
stus:['小王','小李','小芳']
}
addStus=() => {
const {stus}=this.state
stus.unshift('小刘')
this.setState({stus})
}
render() {
console.log('parent---render');
return (
我是Parent组件
学生有{this.state.stus}
)
}
}
需要通过
[新增的数组元素,...原数组]
的方式进行更新
export default class Parent extends PureComponent {
state = {
stus:['小王','小李','小芳']
}
addStus=() => {
const {stus}=this.state
this.setState({stus:['小刘',...stus]})
}
render() {
console.log('parent---render');
return (
我是Parent组件
学生有{this.state.stus}
)
}
}
项目中一般使用PureComponent
来优化
如何向组件内部动态传入带内容的结构(标签)?
Vue中:
使用slot
技术, 也就是通过组件标签体传入结构
React中:
使用children props
: 通过组件标签体传入结构
使用render props
: 通过组件标签属性传入结构,而且可以携带数据,一般用render
函数属性
之前说过标签体的内容是标签特殊的属性
children
,可以通过this.props.children
进行读取,所以可以将B组件作为A组件的标签体写入
export default class Parent extends Component {
render() {
return (
)
}
}
class A extends Component {
state={name:'tom'}
render() {
const {name}=this.state
return (
我是A组件
{this.props.children}
)
}
}
class B extends Component {
render() {
return (
我是B组件
)
}
}
问题: 如果B组件需要A组件内的数据 ==> 做不到
export default class Parent extends Component {
render() {
return (
)
}
}
class A extends Component {
state={name:'tom'}
render() {
const {name}=this.state
return (
我是A组件
{this.props.children}
)
}
}
class B extends Component {
render() {
return (
我是B组件
从A获取到名字:{this.props.name}
)
}
}
}>
A组件:
{this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示{this.props.data}
export default class Parent extends Component {
render() {
return (
我是Parent组件
}/>
)
}
}
class A extends Component {
state={name:'tom'}
render() {
const {name}=this.state
return (
我是A组件
{this.props.render(name)}
)
}
}
class B extends Component {
render() {
return (
我是B组件
从A获取到名字:{this.props.name}
)
}
}
错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
getDerivedStateFromError
配合componentDidCatch
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
// 在render之前触发
// 返回新的state
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// 统计页面的错误。发送请求发送到后台去
console.log(error, info);
}
// Parent组件
export default class Parent extends Component {
state={
hasError:''//用于标识子组件是否产生错误
}
// 出错时的生命周期钩子
componentDidCatch(){
console.log('渲染组件出错');
}
// 当Parent的子组件出现报错时候,会触发getDerivedStateFromError调用,并携带错误信息
static getDerivedStateFromError(error){
console.log(error);
return {hasError:error}
}
render() {
return (
我是Parent组件
{this.state.hasError?网络出错
: }
)
}
}
// Child组件
export default class Child extends Component {
state={
/* users:[
{id:'1',name:'may',age:18},
{id:'2',name:'tom',age:21},
{id:'3',name:'jerry',age:19}
] */
// 错误原因:
users:''
}
render() {
return (
我是Child组件
{
this.state.users.map((userObj)=>{
return - 名字{userObj.name}——年龄{userObj.age}
})
}
)
}
}
props
:children props
和render props
pubs-sub
、event
等等redux
、dva
等等conText
:生产者-消费者模式