1. Portal
官方解释:Portals
提供了一个最好的在父组件包含的DOM
结构层级外的DOM
节点渲染组件的方法。其实它的作用我们可以认为是传送门
, 它能够帮助我们在父组件的外部渲染出子组件,应用场景例如:当子组件需要从视觉上“跳出”其容器时,譬如对话框、悬浮卡、提示框等。
例如antUI
组件库中的弹出框:(在点击的瞬间干组件下弹出一个新的div
)
- 我们也许会说,使用
css
样式中的定位就可以实现,但其实这种方法是存在弊端的,因为在使用css
样式的过程中必然会存在父子之间的层级关系,那么此时当父组件的dom
元素有 overflow:hidden
或者z-inde
样式,子层级就会受到它的影响从而出现问题,但是Portal
不会有这种问题,因为他是可以指定位置的渲染html
结构,也就是所可以避免出现父子层级结构的出现!
案例展示:
APP.js
(根组件)
import React, { Component } from 'react' import './App.css' import PortalDialog from './compoents/PortalDialog' export default class App extends Component { state = { isShow: false } render() { return ( <div className="box" onClick={() => { // portal支持事件冒泡 /* 虽然通过portal渲染的元素在父组件的盒子之外, 但是渲染的dom节点仍在React的元素树上,在那个dom元素上 的点击事件仍然能在dom树中监听到。 */ console.log("box身上监听的事件") }}> {/* 左边盒子 */} <div className="left"></div> {/* 右边盒子 */} <div className="right"> <button onClick={() => { this.setState({ isShow: true }) }}>点击</button> { this.state.isShow && <PortalDialog onClose={() => { // 子传父通信使用props传递回调函数 this.setState({ isShow: false }) }}> <div>插槽元素1</div> <div>插槽元素2</div> <div>插槽元素3</div> </PortalDialog> } </div> </div> ) } }
|
Portal
组件(遮罩层)
import React, { Component } from 'react' import { createPortal } from 'react-dom'
export default class Dialog extends Component { render() { return createPortal( <div style={{ width: '100%', height: '100%', position: 'fixed', left: 0, top: 0, background: 'rgba(0,0,0,0.7)', color: "white" }}> Dialog- <div>loading-正在加载中</div> {/* 支持使用插槽 */} {this.props.children} <button onClick={this.props.onClose}>close</button> </div>, document.body) } }
|
样式代码:
*{ margin:0; padding:0; }
.left,.right{ height: 100vh; }
.box{ display:flex; }
.left{ width: 200px; background:yellowgreen; position: relative; }
.right{ flex:1; background:skyblue; position: relative; }
|
结果展示:
2. 懒加载(Lazy
和 Suspense
)
- 懒加载是什么?预加载又是什么? 我们在项目中正常的引入一些资源或者组件的时候通过是使用
import
或者require
来直接引入的,这会导致项目在启动的时候就会全部加载我们使用的资源或组件,这就是预加载,但是当资源或者组组件过大的时候就会出现项目启动时间过程,用户体验就不好了,这时我们就可以使用React
中的懒加载,所谓懒加载其实我们可以认为是动态加载资源或组件,当我们用到它的时候就加载它,这就可以优化当项目的内容丰富,页面多的时候,首屏同时加载过多的内容,会导致卡顿不流畅响应速度慢、用户等待时间过长等问题。
基本使用:
import React, { Component, Suspense } from 'react'
const Nowplaying = React.lazy(() => import('./components/Nowplaying')) const Comingsoon = React.lazy(() => import('./components/Comingsoon')) export default class App extends Component { state = { type: 1 } render() { return ( <div> {/* 点击修改状态 */} <button onClick={() => { this.setState({ type: 1 }) }}>正在热映</button> <button onClick={() => { this.setState({ type: 2 }) }}>即将上映</button>
{/* 需要使用Suspense来结合使用,不然会报错,且fallback必须要有内容 */} <Suspense fallback={<div>正在加载中....</div>}> { this.state.type === 1 ? <Nowplaying></Nowplaying> : <Comingsoon></Comingsoon> } </Suspense>
</div> ) } }
|
结果展示:
总结:
单独使用React.lazy
时项目是会报错的,因为在React
使用了 lazy
之后,会存在一个加载中的空档期,React
不知道在这个空档期中该显示什么内容,所以需要我们指定。接下来就要使用到suspense
加载指示器,且注意,在Suspense 使用的时候, fallback
一定是存在且有内容的(可以为html片段
,或者一段jsx
代码), 否则会报错。
3. forwordRef
(透传Ref
)
- 引用传递(
Refforwading
)是一种通过组件向子组件自动传递 引用ref
的技术。对于应用者的大多数组件来说没什么作用。但是对于有些重复使用的组件,可能有用。例如某些input
组件,需要控制其focus
,本来是可以使用ref
来 控制,但是因为该input
已被包裹在组件中,这时就需要使用Ref forward
来透过组件获得该`input的引用。可以透传多层
案例展示:
- 在父组件中使用
ref
获取子组件中的input
的DOM
节点
1. 未使用forwardRef
import React, { Component } from 'react'
export default class App extends Component { mytext = null render() { return ( <div> <button onClick={()=>{ // console.log('子组件的DOM节点',this.mytext) this.mytext.current.focus()//获取子组件中input的焦点 this.mytext.current.value=""//清空它的内容 }}>获取焦点</button>
<Child callback={(el)=>{ // 将子组件传递过来的DOM节点赋值给父组件中的临时变量 this.mytext = el }}/> </div> ) } }
class Child extends Component{ mytext= React.createRef()
componentDidMount() { this.props.callback(this.mytext) } render(){ return <div style={{background:"yellow"}}> {/* 使用ref属性获取请指定的DOM元素 */} <input defaultValue="11111111" ref={this.mytext}/> </div> } }
|
2. 使用forwardRef
(透传属性)后
import React, { Component, forwardRef } from 'react'
export default class App extends Component { mytext =React.createRef()
render() { return ( <div> <button onClick={()=>{ console.log('获取到子组件中的DOM元素',this.mytext) this.mytext.current.value=""// 获取到子组件中input的value属性 this.mytext.current.focus()// 获取到子组件中input的焦点 }}>获取焦点</button>
{/* 直接给子组件传递ref属性获取子组件内部的DOM节点 */} <Child ref={this.mytext}/> </div> ) } }
const Child = forwardRef((props,ref)=>{ return <div style={{background:"yello"}}> {/* 标上父子组件传递过来的ref属性 */} <input ref={ref} defaultValue="1111"/> </div> })
|
结果展示:
注意:使用forwardRef
属性只能透传一个Ref
,想要透传多个的话只能使用上面的常规方法了!
4. memo
一般来讲,有父子层级的组件,父组件在重新渲染的时候,里面的子组件也会重新渲染,也就是说:只要父组件的某一个状态改变,父组件下面所有的子组件不论是否使用了该状态,都会进行重新渲染。这就导致了一个问题: 父组件中的状态发生改变,重新渲染是无可避免的,但是如果此时子组件中并没有发生变化,那么此时子组件也重新渲染这个操作就显得多余了,而且还浪费性能!此时就引出了memo
高阶组件,与之类似的还有PureComponent
,两者同样是用于优化子组件频繁渲染的问题的
memo
的作用: memo
是react
的一种缓存技术,这个函数可以检测从父组件接收的props
,并且在父组件改变state
的时候比对这个state
是否是本组件在使用,如果不是,则拒绝重新渲染。
案例展示:
- 在父组件中设置两个按钮,点击第一个按钮,重新渲染父组件但是保持子组件不重新渲染,第二个按钮,重新渲染子组件
import React, { Component, memo } from 'react'
export default class App extends Component { state ={ name:"lam", title:"aaaaaa" } render() { return ( <div> {/* 第一个按钮,点击修改name状态(在父组件中展示) */} <button onClick={()=>{ this.setState({ name:"肥林" }) }}>点击更新name属性(不更新子组件)</button>
{/* 第二个按钮,点击修改title状态(在子组件中展示) */} <button onClick={()=>{ this.setState({ title:"bbbbbbbbbb" }) }}>点击更新title属性(更新子组件)</button><br/>
{/* 展示name属性 */} {this.state.name} {/* 使用props将title状态传给子组件 */} <Child title={this.state.title}/> </div> ) } }
const Child = memo((props)=>{ console.log('子组件重新更新了!') return <div>子组件-{props.title}</div> })
|
结果展示:
memo
与PureComponent
区别
PureComponent
只能用于class
组件,memo
用于functional
组件
memo
存在的问题:
memo
只能进行浅拷贝
来校验决定是否触发重新渲染。例如,当我们在父组件给子组件props
传递一个数组(对象)时,我们修改数组中的一个数组(如push
或者删除),那么此时的子组件中的props
改变了,理论上来讲,子组件应该会重新渲染,但实际上不会! 那是因为我们修改数组时,理论上看似数组变了,实际上,变的只是堆中的数据,而我们的props
是指向存在栈中数据的地址,修改数组时地址是不会改变的,因此memo
检测到的是指向栈的地址没有改变,检测不到栈中的数据有没有改变, 因此要想解决这个问题,就需要向props
传递一个新的数组。
由此可见,memo
只能进行浅拷贝
来校验决定是否触发重新渲染。所以改变数组(对象)
的props
时候记得返回一个全新的数组(对象)
const [list,setList] = useState([1,2,3,4,5]);
setList(list.push(1));
setList([...list,1]);
|