拓展
刀刀
7/6/2023
0 字
0 分钟
setState
更新状态的两种写法
setState(stateChange, [callback])
—— 对象式的setState
statechange
为状态改变对象(该对象可以体现出状态的更改)callback
是可选的回调函数,它在状态更新完毕、界面更新(render
调用)后触发这也说明,
react
状态的更新是异步的。直接获取无法获取到最新的值,需要通过回调函数获取,代码如下所示:jsxadd = () => { const {count} = this.state this.setState({count: count + 1}, () => { console.log('new', this.state.count) // new, 2 }) console.log('old', this.state.count) // old, 1 }
setState(updater, [callback])
——函数式的setState
updater
为返回的stateChange
对象的函数updater
可以接收到state
和props
jsxadd = () => { this.setState(state => ({count: state.count + 1})) }
callback
是可选的回调函数,它在状态更新完毕、界面更新(render
调用)后触发
总结:
对象式的 setState
是函数式的 setState
的简写方式(语法糖)
使用原则如下:
- 如果新状态不依赖于原状态 => 使用对象方式更简便
- 如果新状态依赖原状态 => 使用函数方式更简便
- 如果需要再
setState()
执行后获取最新的状态数据,要在第二个callback
函数中读取
lazyLoad
路由组件懒加载,让路由组件实现需要显示时才加载,提高首屏加载速度。使用方式如下:
在
react
中引入lazy
javascriptimport React, { Component, lazy } from 'react'
把引入的组件变为懒加载组件
javascriptconst Home = lazy(() => import('./Home.vue'))
使用
jsx<Suspense fallback={<h1>loading...</h1>}> <Route path='/home' component={Home}></Route> </Suspense>
通过
Suspense
标签包裹,fallback
表示在等待组件加载时显示的内容,可以是 DOM 标签,也可以是一个引入的组件。注意:
如果使用组件展示
loading
该组件不可使用懒加载!
stateHook
State Hook
让函数组件也可以有 state
状态, 并进行状态数据的读写操作
语法如下所示:
const [xxx, setXxx] = React.useState(initValue)
其中:
- 第一次初始化指定的值在内部作缓存,把
initValue
值赋给 xxx,从第二次开始就直接用缓存的数据,因此不会被initValue
覆盖 - 返回包含2个元素的数组,第1个为内部当前状态值, 第2个为更新状态值的函数,可通过解构获取
setXxx() 拥有2种写法:
setXxx(newValue)
:参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值。如setXxx(xxx + 1)
setXxx(value => newValue)
:参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值。如setXxx(xxx => xxx + 1)
EffectHook
Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
React 中的副作用操作:
- 发ajax请求数据获取
- 设置订阅 / 启动定时器
- 手动更改真实DOM
语法和说明:
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
可以传入第二个参数,表明需要监测谁。如果不写,表示监测所有;如果写空数组,表示谁都不监测;如果写变量名,则只监测该变量的变化,变化才执行里面的代码。
下面实际使用一次:
import React from 'react'
export default function App() {
const [count, setCount] = React.useState(0)
const [name, setName] = React.useState('刀刀')
function countChange() {
setCount(count => count + 1)
}
function nameChange() {
setName('小刀')
}
React.useEffect(() => {
console.log('@');
}, [name])
return (
<>
<div>{count}, {name}</div>
<button onClick={countChange}>数量</button>
<button onClick={nameChange}>名字</button>
</>
)
}
此时只有改变 name
变量才会触发 Effect 回调,改变 count
则不会触发。如果想监听多个,直接在第二个参数的数组内添加即可。
下面有一个案例:我需要在页面挂载完毕后开启定时器,让数量自增1。点击卸载按钮后调用卸载页面的方法,但是卸载后出现报错,信息是页面卸载了但是没有取消定时器。因此需要卸载页面是侦听页面卸载取消定时器。
页面卸载事件是通过 React.useEffect
函数的返回值来触发,当页面卸载后会执行该回调函数的事件。示例代码如下所示:
import React from 'react'
import ReactDOM from 'react-dom'
export default function App() {
const [count, setCount] = React.useState(0)
const [name, setName] = React.useState('刀刀')
React.useEffect(() => {
let timer = setInterval(() => {
setCount(count => count + 1)
}, 1000);
return () => {
clearInterval(timer)
}
}, [])
function unmountedBtn() {
ReactDOM.unmountComponentAtNode(document.getElementById('root'))
}
return (
<>
<div>{count}</div>
<button onClick={unmountedBtn}>卸载</button>
</>
)
}
注意:
- 删掉严格模式,否则影响观测结果:
<React.StrictMode>
- 定时器内状态更新的方法的入参必须写成回调形式
- 严格模式会误导首次渲染,回调形式避免状态受到闭包影响
总结
可以把 useEffect Hook 看做如下三个函数的组合:
componentDidMount()
第二个参数设置空数组
[]
不监听任何变量的变化,只会在组件挂载完毕后触发一次。componentDidUpdate()
第二个参数数组内设置需要监听更新的变量名,当该变量触发更新则会执行其回调函数。
componentWillUnmount()
React.useEffect
返回的回调函数可用于监听页面卸载
RefHook
Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
语法:
const refContainer = useRef()
示例代码:
import React from 'react'
export default function App() {
const myRef = React.useRef()
function show() {
console.log(myRef.current.value);
}
return (
<>
<input ref={myRef} />
<button onClick={show}>展示</button>
</>
)
}
作用:
保存标签对象,功能与
React.createRef()
一样
Fragment
类似于 Vue 的 template
标签,不会被 HTML 渲染成真实 DOM 。
使用
import React, { Fragment } from 'react'
export default function App() {
return (
<Fragment key={1}><Fragment>
// 或
<></>
)
}
二者的区别在于 标签可以添加 key
值,key
值可以更加的方便 diff
算法来进行结点判断。而空标签不允许。
作用
可以不用必须有一个真实的 DOM 根标签了
Context
是一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信。
使用方式:
- 创建Context容器对象:jsx
const XxxContext = React.createContext()
- 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:jsx
<xxxContext.Provider value={数据}> 子组件 </xxxContext.Provider>
- 后代组件读取数据:jsx
//第一种方式:仅适用于类组件 static contextType = xxxContext // 声明接收context this.context // 读取context中的value数据 //第二种方式: 函数组件与类组件都可以 <xxxContext.Consumer> { value => ( // value就是context中的value数据 要显示的内容 ) } </xxxContext.Consumer>
总体示例代码如下所示:
const MyContext = React.createContext()
const { Provider } = MyContext
export default class A extends Component {
state = {username: 'A', age: 23}
render() {
const {username, age} = this.state
return (
<>
<div>A</div>
<Provider value={{username, age}}> // 外面的 {} 是分隔符,里面的 {} 是对象
<B />
</Provider>
</>
)
}
}
class B extends Component {
static contextType = MyContext // 可以这么使用,但没必要。父子组件直接使用props即可。最简单方便
render() {
return (
<>
<div>B</div>
<div>使用:{this.context.age}</div>
<C />
</>
)
}
}
class C extends Component {
static contextType = MyContext // 必须声明接收才可使用
render() {
const {username} = this.context
return (
<>
<div>C</div>
<div>使用:{username}</div>
</>
)
}
}
如果是函数式组件,则无法通过 this
来获取 context
,需要用到另外一个方法 Consumer
,代码如下所示:
const MyContext = React.createContext()
const { Provider, Consumer } = MyContext
export default class A extends Component {
// ...
}
class B extends Component {
// ...
}
function C() {
return (
<>
<div>C</div>
<Consumer>
{
value => `${value.age}-${value.username}`
}
</Consumer>
</>
)
}
注意
在应用开发中一般不用
context
,一般都用它的封装react
插件
PureComponent
Component的2个问题
- 只要执行
setState()
,即使不改变状态数据,组件也会重新render()
效率低 - 只当前组件重新
render()
,就会自动重新render
子组件,纵使子组件没有用到父组件的任何数据 效率低
效率高的做法
只有当组件的 state
或 props
数据发生改变时才重新 render()
原因
Component
中的 shouldComponentUpdate()
总是返回 true
使用
办法1:
重写
shouldComponentUpdate()
方法比较新旧
state
或props
数据,如果有变化才返回true
,如果没有返回false
jsx// 父组件 shouldComponentUpdate(nextProps, nextState) { if(this.state.name === nextState.name) { return false } else { return true } } // 子组件 shouldComponentUpdate(nextProps, nextState) { if(this.props.name === nextProps.name) { return false } else { return true } // 简写形式: // return !(this.props.name !== nextProps.name) }
办法2:
使用
PureComponent
,PureComponent
重写了shouldComponentUpdate()
,只有state
或props
数据有变化才返回true
jsximport React, { Component, PureComponent } from 'react' // 把 Component 换成 PureComponent 即可 class A extends PureComponent{ render() { return ... } }
注意:
- 只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false 。this.setState() 本质上是用新对象替换,修改了引用地址。
- 不要直接修改state数据, 而是要产生新数据
jsxchangeFn = () => { let obj = this.state obj.name = 'A' // 错误写法 this.setState(obj) // 错误写法 }
项目中一般使用PureComponent来优化
renderProps
如何向组件内部动态传入带内容的结构(标签)?
Vue中:
使用
slot
技术, 也就是通过组件标签体传入结构jsx<A><B/></A>
React中:
- 使用
children props
:通过组件标签体传入结构 - 使用
render props
:通过组件标签属性传入结构,而且可以携带数据,一般用render
函数属性
- 使用
children props
react 中,组件内的值是作为一个特殊属性传给子组件。子组件通过 this.props.children
获取。示例代码如下所示:
export defalut class App extends Component {
render() {
return (
<A>
<B />
</A>
<C>xxxx</C>
)
}
}
class A extends Component {
state = {name: 'A'}
render() {
return (
<div>A</div>
<div>{this.props.children}</div>
)
}
}
class B extends Component {
render() {
return (
<div>B</div>
<div>{this.props.children}</div>
)
}
}
class C extends Component {
render() {
return (
<div>C</div>
<div>{this.props.children}</div>
)
}
}
组件中间的值可以输入内容,也可设置一个组件。但是这种写法子组件无法获取父组件内的数据。如上方代码 B 组件无法拿到 A 组件内 state 的数据
render props
export defalut class App extends Component {
render() {
return (
<A render={(name) => <B name={name}></B>} />
)
}
}
class A extends Component {
state = {name: 'A'}
render() {
return (
<div>A</div>
{this.props.render(name)}
)
}
}
class B extends Component {
render() {
return (
<div>B</div>
<div>{this.props.name}</div>
)
}
}
总结
父组件通过下方语句调用 render 把子组件放到自身组件内
jsx{this.props.render(内部state数据)}
子组件通过下方语句显示获取到的数据
jsx{this.props.data}
在顶层标签属性中传递 render
jsx<父组件 render={(data) => <子组件 data={data}></子组件>} />
注意:render 并不是写死的,可以随便起其他名字,如
xiaozhupeiqi={}
,this.props.xiaozhupeiqi(state)
。只不过用render
更加有语义化。
ErrorBoundary
理解
错误边界( Error boundary ):用来捕获后代组件错误,渲染出备用页面
特点
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
使用方式
getDerivedStateFromError 配合 componentDidCatch
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
// 在render之前触发
// 返回新的state
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// 统计页面的错误。发送请求发送到后台去
console.log(error, info);
}
组件通信总结
组件间的关系:
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
几种通信方式:
props:
- children props
- render props
消息订阅-发布:
pubs-sub、event等等
集中式管理:
redux、dva等等
conText:
生产者-消费者模式
比较好的搭配方式:
- 父子组件:props
- 兄弟组件:消息订阅-发布、集中式管理
- 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)