# 使用第三方组件

范例:试用 ant-design组件库

npm install antd --save 
import React, { Component } from 'react' 
import Button from 'antd/lib/button' 
import "antd/dist/antd.css"
class App extends Component {  
    render() {    
        return (      
            <div className="App">        
            <Button type="primary">Button</Button>      
            </div>    
        )  
    } 
} 
export default App

# 配置按需加载

安装react-app-rewired取代react-scripts,可以扩展webpack的配置 ,类似vue.config.js

npm install react-app-rewired customize-cra babel-plugin-import -D
//根目录创建config-overrides.js 
const { override, fixBabelImports,addDecoratorsLegacy } = require('customize-cra');

module.exports = override(
  fixBabelImports('import', {
    libraryName: 'antd',
    libraryDirectory: 'es',
    style: 'css',
  }),
  addDecoratorsLegacy()//配置装饰器 
);

// 修改package.json
"scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-app-rewired eject"
  },

# 支持装饰器配置

npm install -D @babel/plugin-proposal-decorators
import React, { Component } from 'react'
import {Button}  from 'antd';

const foo = Cmp=>props=>{
    return (
        <div className="border" style={{border:'1px solid #aaa'}}>
            <Cmp {...props}></Cmp>
        </div>
    )
}
const foo2 = Cmp=>props=>{
    return (
        <div className="border" style={{border:'1px solid red'}}>
            <Cmp {...props}></Cmp>
        </div>
    )
}
@foo
@foo2
class Child extends Component{
    render(){
        return <div>Child</div>
    }
}

@foo2
class HocPage extends Component {
    render() {
        // const Foo = foo2(foo(Child))
        return (
            <div>
                <h1>HocPage</h1>
                {/* <Foo></Foo> */}
                <Child></Child>
                <Button type="primary" >btn</Button>
            </div>
        )
    }
}
export default HocPage

# 表单组件设计与实现

# 表单组件设计思路路

  • 表单组件要求实现数据收集、校验、提交等特性,可通过高阶组件扩展

  • 高阶组件给表单组件传递一个input组件包装函数接管其输入事件并统一管理表单数据

  • 高阶组件给表单组件传递一个校验函数使其具备数据校验功能

表单基本结构,创建MyFormPage.js

//校验规则 
const nameRules = { required: true, message: "please input your name" };
const passwordRules = { required: true, message: "please input your password" };

class MyFormPage extends Component {
    submit = () => {
        const {getFieldsValue,getFieldValue,validateFields} =this.props
        // console.log('submit',getFieldsValue(),getFieldValue('name'));
        validateFields((err, values) => { 
            if (err) { 
                console.log("err", err); 
            } else { 
                console.log("submit", values); 
            } 
        });
    }
    render() {
        const {getFieldDecorator} =this.props
        return (
            <div>
                <h1>MyFormPage</h1>
                {getFieldDecorator("name",{rules:[nameRules]})(<input type="text"/>)}
                {getFieldDecorator("password",{rules:[passwordRules]})(<input type="password"/>)}
                <button onClick={this.submit}>提交</button>
            </div>
        )
    }
}
export default zFormCreate(MyFormPage)

高阶组件zFormCreate:扩展现有表单,./components/zFormTest.js

import React, { Component } from 'react'

export default function zFormCreate(Cmp){
    return class extends Component{
        constructor(props){
            super(props)
            this.options={} //存储配置字段项
            this.state={} //存储字段值
        }
        handleChange=(e)=>{
            let {name,value}=e.target;
            this.setState({[name]:value})
        }
        getFieldDecorator=(field,option)=>{
            this.options[field]=option;
            return InputCmp=><div className="border">
                { // 由React.createElement生成的元素不能修改,需要克隆一份再扩展
                    React.cloneElement(InputCmp,{
                        name:field,
                        value:this.state[field]||"",
                        onChange:this.handleChange
                    })
                }
            </div>
        }
        getFieldsValue=()=>{
            return {...this.state}
        }
        getFieldValue=(field)=>{
            return this.state[field]
        }
        validateFields=(cb)=>{
            const tmp={...this.state},err=[]
            for(let i in this.options){
                if(tmp[i]===undefined){
                    err.push({[i]:'err'})
                }
            }
            if(err.length>0){
                cb(err,tmp)
            }else{
                cb(undefined,tmp)
            }
            
        }
        render(){
            return (
                <div className="border">
                    <Cmp {...this.props} 
                    getFieldDecorator={this.getFieldDecorator}
                    getFieldsValue={this.getFieldsValue}
                    getFieldValue={this.getFieldValue}
                    validateFields={this.validateFields}
                    ></Cmp>
                </div>
            )
        }
    }
}

# 弹窗类组件设计与实现

# 设计思路

弹窗类组件的要求弹窗内容在A处声明,却在B处展示。react中相当于弹窗内容看起来被render到一个组件里面 去,实际改变的是网页上另一处的DOM结构,这个显然不符合正常逻辑。但是通过使用框架提供的特定API创建 组件实例并指定挂载目标仍可完成任务。

# 具体实现 方案1:Portal

传送门,react v16之后出现的portal可以实现内容传送功能。 它做得事情是通过调用createPortal把要画的东西画在DOM树上另一个角落。

范例:Dialog组件

// DialogPage.jsx
import React, { Component } from 'react'
import Dialog from './components/Dialog'
import {Button} from 'antd'
export default class DialogPage extends Component {
    state={
        visible:false
    }
    change=()=>{
        this.setState({
            visible:!this.state.visible
        })
        setTimeout(()=>{
            console.log(111,this.state.visible);
            
        })
    }
    render() {
        const {visible} = this.state
        return (
            <div>
                <h1>DialogPage</h1>
                {visible&&<Dialog hide={true}>hahahaha</Dialog>}
                <Button onClick={this.change}>Dialog toggle</Button>
            </div>
        )
    }
}

// components/Dialog.jsx
import React, { Component } from 'react'
import {createPortal } from 'react-dom'

export default class Dialog extends Component {
    constructor(props){
        super(props)
        this.node=document.createElement('div')
        document.body.appendChild(this.node)
    }
    componentWillUnmount(){
        document.body.removeChild(this.node)
    }
    render() {
        const {children,hide}=this.props
        return createPortal(
            <div className="border" style={{visibility:hide?"":''}}>
                <h1>Dialog</h1>
                {children}
            </div>,
            this.node
        )
    }
}

# 方案2:unstable_renderSubtreeIntoContainer

在v16之前,实现“传送门”,要用到react中两个秘而不宣的React API

export class Dialog2 extends React.Component {
    render() { return null; }
    componentDidMount() {
        const doc = window.document; this.node = doc.createElement("div"); doc.body.appendChild(this.node);
        this.createPortal(this.props);
    }
    componentDidUpdate() { this.createPortal(this.props); }
    componentWillUnmount() { unmountComponentAtNode(this.node); window.document.body.removeChild(this.node); }
    createPortal(props) {
        unstable_renderSubtreeIntoContainer(
            this, //当前组件      
            <div className="dialog">{props.children}</div>, // 塞进传送门的JSX      
            this.node // 传送门另一端的DOM node    
        );
    }
}

# 树形组件设计与实现

# 设计思路

递归:自己调用自己 react中实现递归组件更加纯粹,就是组件递归渲染即可。假设我们的节点组件是TreeNode,它的render中只要 发现当前节点拥有子节点就要继续渲染自己。节点的打开状态可以通过给组件一个open状态来维护。

import React, { Component } from 'react'
import TreeNode from './components/TreeNode'

//数据源 
const treeData = {
    key: 0,//标识唯⼀一性  
    title: "全国", //节点名称显示  
    children: [    //子子节点数组    
        {
            key: 6, title: "北方区域",
            children: [
                {
                    key: 1, title: "黑龙江省",
                    children: [
                        { key: 6, title: "哈尔滨", },],
                }, 
                { key: 2, title: "北京", },
            ],
        },
        {
            key: 3, title: "南方区域",
            children: [
                { key: 4, title: "上海", }, { key: 5, title: "深圳", },
            ],
        },
    ],
};

export default class TreePage extends Component {
    render() {
        return (
            <div>
                <h1>TreePage</h1>
                <TreeNode data={treeData}></TreeNode>
            </div>
        )
    }
}

// TreeNode.jsx
import React, { Component } from 'react'
import {Icon} from 'antd'

export default class TreeNode extends Component {
    constructor(props){
        super(props);
        this.state={
            expanded:false
        }
    }
    handleExpand=()=>{
        this.setState({
            expanded:!this.state.expanded
        })
    }
    render() {
        const {key,title,children} = this.props.data
        const hasChild=children&&children.length
        const {expanded} = this.state
        return (
            <div className="treeNode">
                <div key={key} onClick={this.handleExpand}>
                {hasChild&&(expanded?<Icon type="caret-down" />:<Icon type="caret-right" />)}
                    <span>{title}</span>
                </div>
                {expanded&&hasChild&&(
                        children.map(item=>{
                            return <TreeNode key={item.key} data={item}></TreeNode>
                        })
                    )}
            </div>
        )
    }
}

# 常见组件优技术

# 定制组件的shouldComponentUpdate钩子

范例:通过shouldComponentUpdate优化组件

import React, { Component } from "react";
export default class CommentList extends Component {
    constructor(props) {
        super(props);
        this.state = {
            comments: []
        };
    }
    componentDidMount() {
        setInterval(() => {
            this.setState({
                comments: [{ author: "小小明", body: "这是小小明写的文文章", }, { author: "小小红", body: "这是小小红写的文文章", },],
            });
        }, 1000);
    }
    render() {
        const { comments } = this.state;
        return (<div>
            <h1>CommentList</h1>
            {comments.map((c, i) => { return <Comment key={i} data={c} />; })}
        </div>);
    }
}
class Comment extends Component {
    shouldComponentUpdate(nextProps, nextState) {
        const { author, body } = nextProps.data;
        const { author: nowAuthor, body: nowBody } = this.props.data;
        if (body === nowBody && author === nowAuthor) {
            return false; //如果不执行这里,将会多次render    
        }
        return true;
    }
    render() {
        console.log("hah");
        const { body, author } = this.props.data;
        return (<div>
            <p>作者: {author}</p>
            <p>正文:{body}</p>
            <p>---------------------------------</p>
        </div>);
    }
}

# PureComponent

定制了shouldComponentUpdate后的Component;

缺点是必须要用class形式,而且要注意是浅比较。

import React, { Component, PureComponent } from "react";
export default class PuerComponentPage extends PureComponent {
    constructor(props) {
        super(props);
        this.state = {
            counter: 0,
            obj: { num: 100, },
        };
    }
    setCounter = () => {
        this.setState({
            counter: 1,
            obj: { num: 200, },
        });
        console.log("setCounter");
    };
    render() {
        console.log("render");
        const { counter, obj } = this.state;
        return (<div>
            <button onClick={this.setCounter}>setCounter</button>
            <div>counter: {counter}</div>
            <div>obj.num: {obj.num}</div>
        </div>);
    }
}

# React.memo

React.memo(...)是React v16.6引进来的新属性。它的作用和React.PureComponent类似,是用来控制函数 组件的重新渲染的。React.memo(...) 其实就是函数组件的React.PureComponent。

import React, { Component, memo } from "react";
export default class MemoPage extends Component {
    constructor(props) {
        super(props);
        this.state = { counter: 0, obj: { num: -1 }, };
    }
    setCounter = () => {
        this.setState({
            counter: 1 /* ,      
                obj: {        num: 100,      }, */,
        });
    };
    render() {
        const { counter } = this.state;
        return (<div>
            <h1>MemoPage</h1>
            <button onClick={this.setCounter}>按钮</button>
            {/* <PuerCounter counter={counter} obj={obj} /> */}
            <PuerCounter counter={counter} />
        </div>);
    }
}
const PuerCounter = memo(props => {
    console.log("render");
    return <div>{props.counter}</div>;
});

Last Updated: 11/22/2021, 1:44:57 PM