-
ReactDOM.createPortal(child, container)
:传送门。将子节点渲染到存在于父组件以外的DOM
节点,即将组件插入到任意DOM
节点;-
child
:任何可渲染的React
子元素,如一个元素、字符串、碎片; -
container
:DOM元素; - 注:它并不会直接操作
DOM
,而是返回一个React组件,必须通过render
渲染才有效。
通常讲,组件只能装配到最近的父元素上,而对于对话框、提示框等组件,需要跳出其容器;class Dialog extends React.Component { constructor(props) { super(props) // 在body下为自己添加一个父级,其实可以直接把body作为自己的父级 this.node = document.createElement('div') document.body.appendChild(this.node) } componentWillUnmount() { // 把自己移除掉 document.body.removeChild(this.node) } render() { return ReactDOM.createPortal( <div className="dialog"> <h1>Dialog</h1> { this.props.children } </div>, this.node ) } } export default function Home(props) { let [ visible, showDialog ] = useState(false) const remove = ev => { showDialog(!visible) } let span = <span name="Machel">Vockty</span> return (<div id="home"> <button onClick={() => showDialog(!visible)}>控制Dialog</button> { visible && <Dialog /> } </div>) }
Portal
可以把组件放置到DOM
树的任何地方。 -
-
React.cloneElement(element, [props], [...children])
克隆组件或DOM
元素,返回一个新的组件。-
element -
克隆的母体组件,React
组件 或 原生DOM
-
props -
可选,配置element
的props
-
children -
可选,为新的React元素添加新的children
,取代从母体中克隆而来的children
export default function Home(props) { const span = <span name="Machel">Vockty</span> return (<div> { React.cloneElement(span, { name: 'Jacky' }, <em>EMChild</em>) } </div>) } // 渲染结果 ==> <div> <span name="Jacky"> <em>EMChild</em> </span> </div>
-
-
react-transition-group
React官方维护的动画库,包括三个核心(组件):Transition、CSSTransition、TransitionGroup
- CSS模块化
import style from './index.module.css' <img className={style.img} />
性能优化
PureComponent
-
PureComponent
是内部定了shouldComponentUpdate
生命周期的组件; -
PureComponent
是一个浅比较,所以务必注意:- 确保数据类型是值类型
- 如果是引用类型,确保地址不变,同时不应该有深层次数据变化
- 低效率更新
import React, { Component } from 'react' class Title extends Component { constructor(props) { super(props) } render() { console.log('Title update') return <div>title: {this.props.title}</div> } } class Count extends Component { constructor(props) { super(props) } render() { console.log('Count update') return <div>Count: {this.props.count}</div> } } export default class App extends Component { constructor(props) { super(props) this.state = { title: "标题", count: 0 } // 每隔2s只更新 count 属性 setInterval(() => { this.setState((preState, preProp) => ({ count: ++preState.count })) }, 2000) } render() { return (<div> <Title title={this.state.title} /> <Count count={this.state.count} /> </div>) } }
-
App
组件中每隔2s
只更新count
属性,虽然子组件Title
中只是用了title
属性,并没有使用count
属性,但依然会被更新!
-
- 使用生命周期函数
shouldComponentUpdate()
判断两次title
是否相同,从而决定是否更新;class Title extends Component { shouldComponentUpdate(nextProps) { return nextProps.title !== this.props.title; } }
-
PureComponent
可以简化shouldComponentUpdate()
的判断import React, { Component, PureComponent } from 'react' class Title extends PureComponent { // 无需任何改变 }
React.memo
React.memo
是一个高阶组件,让函数式组件也拥有 PureComponent
的功能;
const Title = React.memo(props => {
return <p>title: {props.title}</p>
})
组件复合
组件复合:类似于Vue中的插槽,复用组件
React官方:任何一个能用组件继承实现的,都能用组件复合实现;
用React
组件包裹的子元素会被保存在 props.children
中,这样在组件内就可以处理渲染这些子元素。
-
React.Children
处理props.children
的工具。
props.children
的值有三种可能:-
undefined -
当前组件没有子节点 -
object -
有一个子节点 -
array -
多个子节点
使用
React.Children.map
来遍历子节点,就不用担心props.children
的数据类型了。function Dialog(props) { return (<ul> { React.Children.map(props.children, child => { return <li>{ child }</li> }) } </ul>) } export default function Composition() { return (<div> <Dialog> <h2>匿名插槽</h2> <p>复合的标签</p> </Dialog> </div>) }
这就像是
Vue
上的 匿名插槽 -
-
具名插槽不能使用
React.Children
处理- 通过
props
直接传递function Dialog(props) { return <div>{ props.btn }</div> } export default function Composition() { const btn = <button onClick={() => alert('具名插槽')}></button> return (<div> <Dialog color="pink" btn={btn}></Dialog> </div>) }
- 真正意义上的具名插槽
function Dialog(props) { return (<div> { props.children.btn } { props.children.count } </div>) } export default function Composition() { return (<div> <Dialog> {{ btn: <div>我是具名插槽-BTN</div>, count: <div>我是具名插槽-COUNT</div> }} </Dialog> </div>) }
- 通过
-
匿名插槽和具名插槽同时处理
function Dialog(props) { let compArr = [] props.children.forEach(item => { if(item.$$typeof) { // 匿名 compArr.push(item) } else { // 具名 for(let attr in item) { compArr.push(item[attr]) } } }) return <div>{ compArr }</div> } export default function Composition() { return (<div> <Dialog> {{ btn: <div>我是具名插槽-BTN</div>, count: <div>我是具名插槽-COUNT</div> }} <p>这是匿名插槽-1</p> <h5>这是匿名插槽-2</h5> </Dialog> </div>) }
高阶组件
高阶组件:为了提高组件的复用率,抽离具有相同逻辑/展示的组件,扩展组件的功能。
高阶组件其实是一个工厂函数,接收一个组件,返回一个新的组件,这个新组件可以对属性进行包装,也可以重写部分生命周期;
- 简单使用
-
HOCompt.js
const withLearnReact = (Compt) => { // 工厂函数 const NewCompt = (props) => { return <Compt {...props} name="高阶组件" /> } return NewCompt } class HOC extends Component { render() { return (<div> <p>{this.props.title}</p> <p>{this.props.name}</p> </div>) } } export default withLearnReact(HOC)
-
App.js
render() { return <div><HOCompt title="App use" /></div> }
-
- 链式调用
- 如果是纯展示的组件,则返回一个函数式组件;如果需要重写生命周期,则返回类组件;
- 在
HOCompt
组件中,新添加一个返回类组件的函数,并重写类组件的生命周期方法;const withLifeCycle = (Compt) => { class NewCompt extends Component { componentDidMount() { console.log('重写生命周期componentDidMount') } render() { return <Compt {...this.props}></Compt> } } return NewCompt } export default withLifeCycle(withLearnReact(HOC)) // 链式调用
-
withLearnReact(HOC)
返回一个函数式组件,传递了name
和title
,该组件作为withLifeCycle()
的参数,返回一个重写了生命周期方法的类组件。
- 装饰器的方式实现链式调用,更加优雅的方式。结合
Ant Design
的按需加载
对create-react-app
创建的React
项目,借助react-app-rewired
取代react-scripts
,可以扩展webpack
的配置,实现Ant Design
的按需引入npm i antd -S // Ant Design // babel-plugin-import 用于按需加载组件代码和样式的 babel 插件 npm i react-app-rewired customize-cra babel-plugin-import -D // 支持装饰器配置 npm i @babel/plugin-proposal-decorators -D
- 在根目录下新建
config-overrides.js
,添加如下内容:const { override, fixBabelImports, addDecoratorsLegacy, addBabelPlugins } = require('customize-cra'); module.exports = override( fixBabelImports('import', { libraryName: 'antd', libraryDirectory: 'es', style: 'css' }), // 配置装饰器 addDecoratorsLegacy() //配置支持高阶组件的装饰器 //addBabelPlugins(['@babel/plugin-proposal-decorators', { legacy: true }]) )
- 修改
package.json
的scripts
节点"start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-app-rewired eject"
- 不需要再引入全量的
antd.css
,直接引入需要的组件即可,会自动加载组件相关的CSS
import { Button } from 'antd' <Button type="primary">Primary</Button>
- 在根目录下新建
-
HOCompt.js
const withLearnReact = (Compt) => { const NewCompt = (props) => { return <Compt {...props} name="高阶组件" /> } return NewCompt } const withLifeCycle = Compt => { return class extends Component { componentDidMount() { console.log('重写生命周期componentDidMount') } render() { return <div className="border"><Compt {...this.props}></Compt></div> } } } @withLearnReact @withLifeCycle class HOC extends Component { render() { return (<div> <p>{this.props.title}</p> <p>{this.props.name}</p> </div>) } } export default HOC