关于React的这些常用技巧,你应该知道
前言
大家好,我是CoderBin,本文总结了React中的一些常用技巧,相信看完本文的小伙伴都能从中有所收获💪。
创作不易,你们的点赞收藏留言就是我最大的动力💓
如果文中有不对、疑惑的地方,欢迎各位小伙伴们在评论区留言指正🌻
一、 setState
1. setState更新状态的2种写法
setState(stateChange, [callback])
------对象式的 setState- 1.stateChange 为状态改变对象(该对象可以体现出状态的更改)
- 2.callback 是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
setState(updater, [callback])
------函数式的 setState- 1.updater 为返回 stateChange 对象的函数。
- 2.updater 可以接收到 state 和 props。
- 3.callback 是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
2. 代码示例
import React, { Component } from 'react'
export default class SetState extends Component {
state = { count: 0 }
increment = () => {
// !组件的状态更新是异步的
// * 对象形式的 setState
// const { count } = this.state
// // setState() 第二个参数为render执行之后的回调
// this.setState({ count: count + 1 }, () => {
// console.log(this.state.count);
// })
// * 函数式的 setState
// setState(updateFn(state,props), cb) , 第二个参数cb为render执行之后的回调
this.setState((state, props) => {
// state 就是维护的状态,props为组件接收的属性值
// console.log(state, props);
return { count: state.count+1 }
},)
}
render() {
return (
<div>
<h2>当前求和为:{this.state.count}</h2>
<button onClick={this.increment}>点击+1</button>
</div>
)
}
}
3. 总结
- 对象式的
setState
是函数式的setState
的简写方式(语法糖) - 使用原则:
- 如果新状态不依赖于原状态 ===> 使用对象方式
- 如果新状态依赖于原状态 ===> 使用函数方式
- 如果需要在
setState()
执行后获取最新的状态数据, 要在第二个 callback 函数中读取
二、lazyLoad 组件懒加载
1. 路由组件的lazyLoad
import React, { Component, lazy, Suspense } from 'react'
import { NavLink, Route } from 'react-router-dom'
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Home = lazy(() => import('./Home'))
const About = lazy(() => import('./About'))
export default class Lazyload extends Component {
render() {
return (
<div>
{/* 编写路由链接 */}
<NavLink to='/home'>home</NavLink>
<NavLink to='/about'>about</NavLink>
{/* 注册路由 */}
{/*2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面*/}
{/* fallback 表示在跳转的过程中,展示的东西,可以是标签,或者一个组件(必须是正常引入的组件) */}
<Suspense fallback={<h1>loading...</h1>}>
<Route path="/home" component={Home}/>
<Route path="/about" component={About}/>
</Suspense>
</div>
)
}
}
2. 总结
- 路由懒加载必须借助 react 中的
lazy
函数和Suspense
组件 - 路由的导入方式:
const Home = lazy(() => import('./Home'))
- 注册路由要用
<Suspense></Suspense>
包裹
三、Hooks
1. React Hook/Hooks是什么?
- Hook是React 16.8.0版本增加的新特性/新语法
- 可以让你在函数组件中使用 state 以及其他的 React 特性
2. 三个常用的Hook
- State Hook:
React.useState()
- Effect Hook:
React.useEffect()
- Ref Hook:
React.useRef()
(1). State Hook: React.useState()
(2). Effect Hook: React.useEffect()
(3). Ref Hook: React.useRef()
3. State Hook
- State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
- 语法:
const [xxx, setXxx] = React.useState(initValue)
- useState()说明:
- 参数: 第一次初始化指定的值在内部作缓存
- 返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
- setXxx()2种写法:
- setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
- setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
代码示例
import React from 'react'
export default function Hook() {
// count 为初始值,setCount 为改变初始值的函数
// * 1. 为每一个状态单独开一个 useState()
// (官方建议为每一个状态单独进行管理,不推荐使用对象形式)
const [count, setCount] = React.useState(0)
// 点击 + 1
const increment = () => {
// 直接传入参数调用,就可以改变初始值
setCount(count + 1) // 第一种方式
// setCount((count) => count + 1) // 第二种方式
}
return (
<div>
<h2>当前求和为:{ count }</h2>
<button onClick={increment}>点击+1</button>
</div>
)
}
4. Effect Hook
-
Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
-
React中的副作用操作:发ajax/axios请求数据获取、设置订阅 / 启动定时器、手动更改真实DOM
-
语法和说明:
useEffect(() => { // 在此可以执行任何带副作用操作 return () => { // 在组件卸载前执行 // 在此做一些收尾工作, 比如清除定时器/取消订阅等 } }, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
-
可以把 useEffect Hook 看做如下三个生命周期函数的组合
- componentDidMount()
- componentDidUpdate()
- componentWillUnmount()
5. Ref Hook
- Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
- 语法:
const refContainer = useRef()
- 作用:保存标签对象,功能与
React.createRef()
一样
代码示例
import React from 'react'
export default function Hook() {
const [input, setInput] = React.useState('')
// * useRef(),创建一个容器,与类式组件中的createRef相同的用法
const myRef = React.useRef()
// 展示输入的数据
function show(e) {
setInput(e.target.value)
}
return (
<div>
<h2>你的输入:{input }</h2>
{/* ref 的值即为上面创建的 myRef 容器*/}
<input type="text" ref={myRef} onInput={show} placeholder='请输入'/>
</div>
)
}
四、Fragment
1. 作用
可以不用必须有一个真实的DOM根标签了
2. 代码示例
import React, { Component, Fragment } from 'react'
export default class FragmentDemo extends Component {
render() {
return (
// 作用:可以不用必须有一个真实的DOM根标签了
// <></> 空标签也可以
// 如果要遍历,就使用Fragment,因为可以添加key值
<Fragment key={1}>
<input type="text" />
<input type="text" />
</Fragment>
)
}
}
五、Context 上下文对象
1.理解
一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
2. 使用
-
创建Context容器对象:
const XxxContext = React.createContext()
-
渲染子组件时,外面包裹xxxContext.Provider, 通过 value 属性给后代组件传递数据:
<xxxContext.Provider value={数据}> 子组件 </xxxContext.Provider>
-
后代组件读取数据:
//第一种方式:仅适用于类组件 static contextType = xxxContext // 声明接收context this.context // 读取context中的value数据 //第二种方式: 函数组件与类组件都可以 <xxxContext.Consumer> { value => ( // value就是context中的value数据 要显示的内容 ) } </xxxContext.Consumer>
3. 代码示例
import React, { Component } from 'react'
import './index.css'
// 创建 Context 容器组件
const MyContext = React.createContext()
// 父组件
export default class A extends Component {
state = {
username: 'coderbin',
age: 21
}
render() {
const { username, age} = this.state
return (
<div className='parent'>
<h2>我是 A 组件</h2>
<h3>我的用户名是:{this.state.username}</h3>
{/* 注意:这里必须通过 value属性向后代传递数据 */}
<MyContext.Provider value={{username, age}}>
<B />
</MyContext.Provider>
</div>
)
}
}
// 子组件
class B extends Component{
render() {
return (
<div className='child'>
<h2>我是 B 组件</h2>
<C />
</div>
)
}
}
// 孙组件
// 类式组件接收数据的方法
// class C extends Component{
// // 声明接收 context
// static contextType = MyContext
// render() {
// const {username, age} = this.context
// return (
// <div className='grand'>
// <h2>我是 C 组件</h2>
// <h3>我是从A组件接收到的用户名:{username},年龄是:{ age }</h3>
// </div>
// )
// }
// }
// 函数式组件接收数据的方法
// 使用 <MyContext.Consumer> 包裹,在里面接收展示数据
function C() {
return (
<div className='grand'>
<h2>我是 C 组件</h2>
<h3>我是从A组件接收到的用户名:
<MyContext.Consumer>
{value => `${value.username},年龄是:${value.age}`}
</MyContext.Consumer>
</h3>
</div>
)
}
4. 注意
在应用开发中一般不用context, 一般都用它的封装react插件
六、组件优化
1. Component的2个问题
- 只要执行
setState()
,即使不改变状态数据, 组件也会重新 render() ==> 效率低 - 只要当前组件重新 render(), 就会自动重新 render 子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
2. 效率高的做法
- 只有当组件的 state 或 props 数据发生改变时才重新 render()
3. 原因
- Component 中的
shouldComponentUpdate()
总是返回 true
4. 解决
- 办法1:
- 重写
shouldComponentUpdate()
方法 - 比较新旧 state 或 props 数据, 如果有变化才返回 true, 如果没有返回 false
- 重写
- 办法2:
- 使用
PureComponent
PureComponent
重写了shouldComponentUpdate()
, 只有 state 或 props 数据有变化才返回 true
- 使用
- 注意:
- 只是进行 state 和 props 数据的浅比较, 如果只是数据对象内部数据变了, 返回 false
- 不要直接修改 state 数据, 而是要产生新数据
- 项目中一般使用
PureComponent
来优化
5. 代码示例
import React, { PureComponent } from 'react'
import './index.css'
// PureComponent 重写了 shouldComponentUpdate(),
// 只有 state 或 props数据有变化才返回true
// 注意:
// 只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
// 不要直接修改state数据, 而是要产生新数据
export default class Parent extends PureComponent {
state = {
carName: '奔驰'
}
changeCar = () => {
this.setState({
carName: '法拉利'
})
}
// shouldComponentUpdate(nextProps, nextState) {
// // 当前值
// console.log(this.props, this.state);
// // 接下来要变化的目标 Props,目标 State
// console.log(nextProps, nextState);
// return true
// }
render() {
console.log('Parent -- render');
const { carName } = this.state
return (
<div className='parent'>
<h3>我是 Parent 组件</h3>
<span>我的车的名字是:{carName}</span><br />
<button onClick={this.changeCar}>点击换车</button>
<Child carName={carName} />
</div>
)
}
}
class Child extends PureComponent {
render() {
console.log('Child -- render');
return (
<div className='child'>
<h3>我是 Parent 组件</h3>
<span>我接收到的车是:{ this.props.carName }</span>
</div>
)
}
}
七、render props
1. 如何向组件内部动态传入带内容的结构(标签)?
Vue中:
- 使用 slot 插槽技术, 也就是通过组件标签体传入结构
<A><B/></A>
React中:
-
- 使用 children props: 通过组件标签体传入结构
-
- 使用 render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
2. children props
- 父组件:
<A> <B>xxxx</B></A>
- 子组件:
{this.props.children}
问题:如果 B 组件需要 A 组件内的数据, ==> 做不到
3. render props
- 最外层组件:
<A render={(data) => <B data={data}></B>}></A>
- A组件:
{this.props.render(内部state数据)}
- B组件:读取A组件传入的数据显示
{this.props.data}
4. 代码示例
import React, { Component } from 'react'
import './index.css'
export default class RenderProps extends Component {
render() {
return (
<div className='parent'>
<h3>我是 Parent 组件</h3>
{/* 1. 给 A 组件一个 render 属性,值为一个函数,里面返回 B 组件 */}
{/* 2. 这个函数可以接受一个来自 A 组件的参数,传递给 B 组件 */}
<A render={(name) => <B name={name} />} />
</div>
)
}
}
class A extends Component {
state = { name: 'coderbin'}
render() {
const { name } = this.state
return (
<div className='a'>
<h3>我是 A 组件</h3>
{/* 1. 接收传过来的 render,直接调用即可渲染 B 组件*/}
{/* 2. 调用的时候可以传递一个数据 */}
{this.props.render(name)}
</div>
)
}
}
class B extends Component {
render() {
console.log('接收到了 A 组件传递的数据:',this.props.name);
return (
<div className='b'>
<h3>我是 B 组件</h3>
</div>
)
}
}
八、错误边界
1. 理解
错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面
2. 特点
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
3. 使用方式
两个生命周期函数:getDerivedStateFromError
配合 componentDidCatch
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
// 在render之前触发
// 返回新的state
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// 统计页面的错误。发送请求发送到后台去
console.log(error, info);
}
4. 代码示例
Child.jsx
import React, { Component } from 'react'
export default class Child extends Component {
state = {
users:[
{id:'001',name:'tom',age:18},
{id:'002',name:'jack',age:19},
{id:'003',name:'peiqi',age:20},
]
// 测试错误的情况
// users:'abc'
}
render() {
return (
<div>
<h2>我是Child组件</h2>
{
this.state.users.map((userObj)=>{
return <h4 key={userObj.id}>{userObj.name}----{userObj.age}</h4>
})
}
</div>
)
}
}
Parent.jsx
import React, { Component } from 'react'
import Child from './Child'
export default class Parent extends Component {
state = {
hasError:'' //用于标识子组件是否产生错误
}
//当 Parent的子组件出现报错时候,会触发 getDerivedStateFromError调用,并携带错误信息
static getDerivedStateFromError(error){
console.log('@@@',error);
return {hasError:error}
}
// 生命周期函数:组件挂载过程中,如果子组件出了错误,就会调用这个钩子
componentDidCatch(){
console.log('此处统计错误,反馈给服务器,用于通知编码人员进行bug的解决');
}
render() {
return (
<div>
<h2>我是Parent组件</h2>
{this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child/>}
</div>
)
}
}
九、组件通信方式总结
1. 组件间的关系:
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
2. 几种通信方式:
- props:
- children props
- render props
- 消息订阅-发布:pubs-sub、event等等
- 集中式管理:redux、dva等等
- conText:生产者-消费者模式
3. 比较好的搭配方式:
- 父子组件:props
- 兄弟组件:消息订阅-发布、集中式管理
- 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
每文一句:立志宜思真品格,读书须尽苦功夫。
本文列举了对 React 的一些常用写法技巧。当然,关于 React 的这部分知识远远不仅如此,React 中我们需要掌握的知识还非常多。在后续的文章中也会继续跟各位分享更多的 React 知识。有疑问的小伙伴可以在评论区留言,大家一起探讨学习!
- 点赞
- 收藏
- 关注作者
评论(0)