关于 React 的几个常用技巧
一、 setState
1. setState更新状态的2种写法
setState(stateChange, [callback])
------对象式的 setState- 1.stateChange 为状态改变对象(该对象可以体现出状态的更改)
- 2.callback 是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
setState(updater, [callback])
------函数式的 setState- 1.updater 为返回 stateChange 对象的函数,可以接收到 state 和 props
- 2.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 // 声明接收contextthis.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(开发用的少,封装插件用的多)
每文一句:立志宜思真品格,读书须尽苦功夫。