一、弹窗类组件设计与实现
弹窗类组件要求:弹窗内容在A处生命,却在B处展示。react中相当于弹窗内容看起来被render到一个组件里面去,实际改变的是网页上另一处的DOM结构,这个显然不符合正常逻辑。但是通过使用框架提供的特定API创建组件实例并指定挂载目标仍可完成任务。
DialogPage.js文件内容如下:
import React, { Component } from 'react'
import Dialog from '../components/Dialog'
export default class DialogPage extends Component {
constructor(props) {
super(props);
this.state = {
showDialog: false
}
}
setDialog = () => {
this.setState({
showDialog: !this.state.showDialog
})
}
render() {
const { showDialog } = this.state;
return (
<div className='dialogPage'>
<h3>DialogPage</h3>
<button onClick={this.setDialog}>toggle</button>
{/* Dialog在当前组件声明,但是却在body中另一个div中显示 */}
{showDialog && <Dialog />}
</div>
)
}
}
Dialog.js文件内容如下:
import React, { Component } from 'react';
import {createPortal} from 'react-dom';
export default class Dialog extends Component {
constructor(props) {
super(props);
const doc = window.document;
this.node = doc.createElement('div');
doc.body.appendChild(this.node);
}
componentWillUnmount() {
if(this.node) {
window.document.body.removeChild(this.node);
}
}
render() {
return createPortal(
<div className='dialog'>
<h3>Dialog</h3>
</div>,
this.node
)
}
}
执行结果:
总结:
Dialog做的事情是通过调用createPortal把要画的东西画在DOM树上另一个角落。
二、高阶组件HOC
为了提高组件复用率,可测试性,就要保证组件功能单一性;但是若要满足复杂需求,就要扩展功能单一的组件,在React里就有了HOC(Higher-Order Components)的概念。
定义:高阶组件是参数为组件,返回值为新组件的函数。
import React, { Component } from 'react'
// hoc
// 是个函数,参数是组件,返回值是个新的组件
const foo = Cmp => props => {
return (
<div className="border" style={{width: "20%", border: "1px solid red", padding: "20px"}}>
<Cmp {...props} name="omg" />
</div>
)
};
function Child(props) {
return <div>Child</div>
}
const Foo = foo(foo(Child))
export default class HocPage extends Component {
render() {
return (
<div>
<h3>HocPage</h3>
<Foo />
</div>
)
}
}
执行结果:
装饰器写法
1.安装插件,改变webpack配置:
yarn add -D react-app-rewired customize-cra
yarn add -D @babel/core @babel/plugin-proposal-decorators @babel/preset-env
2.修改package.json文件中 scripts 脚本:
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
}
3.在项目根目录下创建 config-overrides.js 并写入以下内容:
// 配置完成后记得重启下
const path = require('path')
const { override, addDecoratorsLegacy } = require('customize-cra')
function resolve(dir) {
return path.join(__dirname, dir)
}
const customize = () => (config, env) => {
config.resolve.alias['@'] = resolve('src')
if (env === 'production') {
config.externals = {
'react': 'React',
'react-dom': 'ReactDOM'
}
}
return config
};
module.exports = override(addDecoratorsLegacy(), customize())
4.在项目根目录下创建 .babelrc 并写入以下内容:
{
"presets": [
"@babel/preset-env"
],
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
]
}
基本完成以上步骤就可以正常使用装饰器了,再也不会报 @ 的错误了。同时Support for the experimental syntax ‘decorators-legacy’ isn’t currently enabled这个错误也将消失。
注意事项
问:出现如下问题,如何修复?
答:在 “tsconfig” 或 “jsconfig” 中设置 “experimentalDecorators” 选项以删除此警告。
code=>首选项=>设置 => 搜索experimentalDecorators => 打上勾勾
HocPage.js文件内容如下:
import React, { Component } from 'react'
// hoc
// 是个函数,参数是组件,返回值是个新的组件
const foo = Cmp => props => {
return (
<div className="border" style={{width: "20%", border: "1px solid red", padding: "20px"}}>
<Cmp {...props} name="omg" />
</div>
)
};
// 装饰器只能用在class上
// 执行顺序从下往上
@foo
class ClassChild extends Component {
render() {
return (
<div>ClassChild--{this.props.name}</div>
)
}
}
export default class HocPage extends Component {
render() {
return (
<div>
<h3>HocPage</h3>
<ClassChild />
</div>
)
}
}
执行结果:
组件是将 props 转换为UI,而高阶组件是将组件转换为另一个组件
HOC在React的第三方库中很常见,例如 React-Redux 的 connect。
使用 HOC 的注意事项
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC本身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
不要在 render 方法中使用 HOC
React 的 diff 算法(称为协调)使用组件标识来确定它是应该更新现有子树还是将其丢弃并挂载新子树。如果从 render 返回的组件与前一个渲染中的组件相同,则 React 通过将子树与新子树进行区分来递归更新子树。如果它们不相等,则完全卸载前一个子树。
render() {
// 每次调用render函数都会创建一个新的EnhancedComponent
// EnhancedComponent1 !== EnhancedComponent2
const EnhancedComponent = enhance(MyComponent);
// 这将导致子树每次渲染都会进行卸载,和重新挂载的操作
return <EnhancedComponent />
}
这不仅仅是性能问题-重新挂载组件会导致该组件及其所有子组件的状态丢失。