07-React Hooks(路由组件懒加载, Context上下文, 组件优化...)
扩展
setState
(1). setState(stateChange, [callback])------对象式的setState 1.stateChange为状态改变对象(该对象可以体现出状态的更改) 2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用 (2). setState(updater, [callback])------函数式的setState 1.updater为返回stateChange对象的函数。 2.updater可以接收到state和props。 3.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。 总结: 1.对象式的setState是函数式的setState的简写方式(语法糖) 2.使用原则: (1).如果新状态不依赖于原状态 ===> 使用对象方式 (2).如果新状态依赖于原状态 ===> 使用函数方式 (3).如果需要在setState()执行后获取最新的状态数据, 要在第二个callback函数中读取
LazyLoader
路由组件的懒加载
# 导入库 import React, {lazy,Suspense} from 'react'; //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>
Hooks
简介
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
useState
/** * 使用范围: 用于函数式组件, 使函数式组件具备state的能力 * useState的使用方式 * 1: 从react库中引入 useState 函数 * 2: 使用函数创建值引用和方法引用 * 2.1: const [count, setCount] = useState(0) * 2.2: 调用useState 入参为初次属性初始化的默认值 * 2.3: 返回值为数组,一般使用结构的方式获取回来, 第一个引用为值对象, 第二个引用为该值对象的赋值函数 * 3: 渲染方式, 直接通过 {count} 渲染 * 4: 赋值方式: 调用赋值函数 * 4.1: 入参为值对象修改 setCount(count+1) * 4.2: 入参为函数修改: setCount(count => count + 1) 函数会有一个入参为当前值对象, 然后需要返回一个新的值对象 */ import React, {useState} from 'react'; function Index(props) { const [count, setCount] = useState(0) const add = () => { // setCount(count+1) setCount(count => count + 1) } return ( <div> <h2>当前求和为:{count}</h2> <button onClick={add}>+1</button> </div> ); } export default Index;
useEffect
/** * 使用范围: 用于函数式组件, 使函数式组件具备生命周期钩子的能力,可以看做是 * componentDidMount,componentDidUpdate,componentWillUnmount * 三个生命周期钩子函数的集合 * useEffect的使用方式 * 1: 从react库中引入 useEffect 函数 * 2: 使用函数完成生命周期钩子函数 * -:具体使用看下面注释 * */ import React, {useState, useEffect} from 'react'; function Index(props) { // useState const [count, setCount] = useState(0) const [sum, setSum] = useState(0) const [he, setHe] = useState(0) /** * 实现componentDidMount * useEffect 第二个参数[] 什么也不写, 就是代表不监听任何state的变化, 只有在第一次渲染的时候执行 */ useEffect(() => { // setCount(count+1) // 实现count自动累加 const timer = setInterval(() => { // 这里有个问题, 需要使用函数式入参, 不能直接使用值入参, 因为值入参是异步的, 函数的话会接受到上一次的值 setCount(count => count + 1) }, 1000) }, []) /** * 实现componentDidMount+componentDidUpdate * useEffect 第二个参数[] 里面写了那些state的值对象, 当这些值对象发生变化时,就会执行这个函数 */ useEffect(() => { // 当count改变的时候sum自动加1 if (count !== 0) { setSum(sum => sum + 1) } }, [count]) /** * 实现componentDidMount+componentDidUpdate+componentWillUnmount * useEffect 函数, 可以返回一个函数, 这个返回的函数就是componentWillUnmount生命周期钩子, 所有清除定时器,取消订阅等操作就可以写在这个函数里面 */ useEffect(() => { // 当count改变的时候sum自动加1 const timer = setInterval(() => { // 这里有个问题, 需要使用函数式入参, 不能直接使用值入参, 因为值入参是异步的, 函数的话会接受到上一次的值 setHe(he => he + 1) }, 1000) return () => { clearInterval(timer) } }, []) return ( <div> <h2>当前求和为:{count}</h2> <h2>当前求和为:{sum}</h2> <h2>当前求和为:{he}</h2> </div> ); } export default Index;
useRef
/** * 使用范围: 用于函数式组件, 使函数式组件具备React.createRef的能力 * useRef的使用方式 * 1: 从react库中引入useRef函数 * 2: 使用函数创建属性 const myRef = useRef() * 3: 绑定到组件 <input ref={myRef} type="text"/> * 4: 获取值 myRef.current.value */ import React, {useRef} from 'react'; function Index(props) { const myRef = useRef() const show = () => { console.log(myRef.current.value) } return ( <div> <input ref={myRef} type="text"/> <button onClick={show}>显示</button> </div> ); } export default Index;
Fragment
/** * Fragment : 代码片段标签, 在React渲染时会被丢弃 * 使用方式: * 1: 从react库中引入 * 2: 一般包裹在最外层 * 3: 只接受唯一一个属性 key * 4: 如不过想写, 可以使用空标签替换 <></> */ import React, {Fragment} from 'react'; function Index(props) { return ( <Fragment key={'fg'}> <h2>代码片段:</h2> </Fragment> ); } export default Index;
Context+useContext(Hooks)
/** * Context: 上下文对象, 一般用于多层次组件传递值 * 使用方式: * 1: 从react中引入React * 2: 创建: const UserNameContext = createContext('dance') * -: 看下方注释 */ import React, {Component, useState, useContext, createContext} from 'react'; // 创建Context对象 const UserNameContext = createContext('dance') function Main(props) { const [userName, setUserName] = useState('tom'); return ( <> <h2>我是Main组件</h2> <h3>我的用户名是:{userName}</h3> <hr/> {/* 通过value属性传入参数, 所有的子组件就都可以获取到context */} <UserNameContext.Provider value={userName}> <A/> </UserNameContext.Provider> </> ); } function A(props) { return ( <> <h2>我是A组件</h2> {/* 通过标签Consumer获取,并渲染 */} <UserNameContext.Consumer> { value => (<h3>接受到的用户名是:{value}</h3>) } </UserNameContext.Consumer> <hr/> <B/> </> ); } /** * 函数式组件接受Context */ function B(props) { // 通过useContext函数获取,并渲染 let context = useContext(UserNameContext) return ( <> <h2>我是B组件</h2> <h3>接受到的用户名是:{context}</h3> <C/> </> ); } /** * 类组件 接受Context */ class C extends Component { // 类组件通过属性获取,并渲染 static contextType = UserNameContext render() { return ( <> <h2>我是C组件</h2> <h3>接受到的用户名是:{this.context}</h3> </> ); } } export default Main;
组件优化
Component的两个问题
- 只要执行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来优化
但是一般项目开发中都是用函数式组件+hooks来写的
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组件内的数据, ==> 做不到
render props
<A render={(data) => <C data={data}></C>}></A> A组件: {this.props.render(内部state数据)} C组件: 读取A组件传入的数据显示 {this.props.data}
错误边界
- 理解:
- 错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面
- 特点:
- 只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
- 使用方式:
- getDerivedStateFromError配合componentDidCatch
// 生命周期函数,一旦后台组件报错,就会触发 static getDerivedStateFromError(error) { console.log(error); // 在render之前触发 // 返回新的state return { hasError: true, }; } componentDidCatch(error, info) { // 统计页面的错误。发送请求发送到后台去 console.log(error, info); }
组件通信方式总结
组件间的关系:
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
几种通信方式:
1.props: (1).children props (2).render props 2.消息订阅-发布: pubs-sub、event等等 3.集中式管理: redux、dva等等 4.conText: 生产者-消费者模式
比较好的搭配方式:
父子组件:props 兄弟组件:消息订阅-发布、集中式管理 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)