一、彈窗類組件設計與實現
彈窗類組件要求:彈窗內容在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 />
}
這不僅僅是性能問題-重新掛載組件會導致該組件及其所有子組件的狀態丟失。