二、React组件化

使用第三方组件

安装:npm install antd --save

试用 ant-design 组件库

import React, { Component } from 'react'
import Button from 'antd/lib/button';
import 'antd/dist/antd.css'

export default class TestAntd extends Component {
  render() {
    return (
      <div>
        <Button type="primary">Button</Button>
      </div>
    )
  }
}

上面 import 的内容太长,不利于日常开发

配置按需加载

安装 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() // 配置装饰器,这里如果配置,需要先安装下面的npm
)

修改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'

const foo = Cmp => props => {
  return <div 1px solid red'}}>
    <Cmp {...props}/>
  </div>
}

const foo2 = Cmp => props => {
  return <div 1px solid green', padding: '10px'}}>
    <Cmp {...props}/>
  </div>
}

function Child(props){
  return <div>Child</div>
}

export default class HocPage extends Component {
  render() {
    const Foo = foo2(foo(Child))
    return (
      <div>
        <h1>HocPage</h1>
        <Foo />
      </div>
    )
  }
}

改为装饰器的写法:装饰器只能用于装饰 class 组件

import React, { Component } from 'react'

const foo = Cmp => props => {
  return <div 1px solid red'}}>
    <Cmp {...props}/>
  </div>
}

const foo2 = Cmp => props => {
  return <div 1px solid green', padding: '10px'}}>
    <Cmp {...props}/>
  </div>
}

@foo2
@foo
class Child extends Component {
  render(){
    return <div>Child</div>
  }
}

export default class HocPage extends Component {
  render() {
    return (
      <div>
        <h1>HocPage</h1>
        <Child />
      </div>
    )
  }
}

注意:需要引入 antd样式文件: import 'antd/dist/antd.css';

使用antd的Form表单

import React, { Component } from 'react'
import {Form, Input, Icon, Button} from 'antd'

const FormItem = Form.Item

export default class FormPage extends Component {
  constructor(props){
    super(props)
    this.state = {
      name: '',
      password: ''
    }
  }
  change = (field, event)=>{
    this.setState({
      [field]: event.target.value
    })
  }
  submit = ()=>{
    console.log('state', this.state);
  }
  render() {
    return (
      <div>
        <h1>FormPage</h1>
        <Form>
          <FormItem label="姓名">
            <Input prefix={<Icon type="user"/>} onChange={event => this.change('name', event)}/>
          </FormItem>
          <FormItem label="密码">
            <Input type="password" prefix={<Icon type="lock"/>}  onChange={event => this.change('password', event)}/>
          </FormItem>
          <FormItem>
            <Button type="primary" onClick={this.submit}>提交</Button>
          </FormItem>
        </Form>
      </div>
    )
  }
}

Form中内容的使用 --- Form.create()

import React, {Component} from 'react'
import {Form, Input, Icon, Button} from 'antd'

const FormItem = Form.Item

class FormPageDecorators extends Component {
  submit = () => {
    const { getFieldsValue, getFieldValue } = this.props.form
    // 获取所有Field的值
    console.log('submit', getFieldsValue());
    // 获取单个值
    console.log('submitName', getFieldValue('name'));
  }
  render() {
    // 装饰器
    const {getFieldDecorator} = this.props.form
    console.log(this.props.form);
    return (
      <div>
        <h1>FormPageDecorators</h1>
        <Form>
          <FormItem label="姓名">
            { getFieldDecorator('name')(<Input prefix={< Icon type = "user" />}/> )}
          </FormItem>
          <FormItem label="密码">
          { getFieldDecorator('password')(<Input type="password" prefix={< Icon type = "lock" />}/> )}
          </FormItem>
          <FormItem>
            <Button type="primary" onClick={this.submit}>提交</Button>
          </FormItem>
        </Form>
      </div>
    )
  }
}
export default Form.create()(FormPageDecorators)

表单验证

// 校验规则
const nameRule = {
  required: true,
  message: 'please input your name'
}
const passwordRule = {
  required: true,
  message: 'please input your password'
}

class FormPageDecorators extends Component {
  submit = () => {
    const { validateFields } = this.props.form
    // 表单验证
    validateFields((err, values)=>{
      if(err){
        console.log('err:', err)
      }else{
        console.log('submit:', values);
      }
    })
  }
}

<FormItem label="姓名">
  { getFieldDecorator('name', {rules: [nameRule]})(<Input prefix={< Icon type = "user" />}/> )}
</FormItem>
<FormItem label="密码">
{ getFieldDecorator('password', {rules: [passwordRule]})(<Input type="password" prefix={< Icon type = "lock" />}/> )}
</FormItem>

自己写一个Form.create()

import React, { Component } from 'react'

function kFormCreate(Cmp){
  return class extends Component {
    constructor(props){
      super(props)
      this.options = {} // 配置字段项
      this.state = {} // 存字段值
    }
    handleChange = (event)=>{
      const {name, value} = event.target
      this.setState({
        [name]: value
      })
    }
    getFieldDecorator = (field, options)=>{
      this.options[field] = options
      return InputCmp=>(
        <div className="border">
          {React.cloneElement(InputCmp,{
            name: field,
            value: this.state[field] || "",
            onChange: this.handleChange
          })}
        </div>
      )
    }
    getFieldsValue = ()=>{
      return {...this.state}
    }
    getFieldValue = (field)=>{
      return this.state[field]
    }
    validateFields = (callback)=>{
      const tem = {...this.state}
      const err = []
      for(let i in this.options){
        if(tem[i] === undefined){
          err.push({
            [i]: 'error'
          })
        }
      }
      if(err.length>0){
        callback(err, tem)
      }else{
        callback(undefined, tem)
      }
    }
    render(){
      return (
        <div className="border">
          <Cmp {...this.props}
            getFieldDecorator={this.getFieldDecorator}
            getFieldsValue={this.getFieldsValue}
            getFieldValue={this.getFieldValue}
            validateFields={this.validateFields}
          />
        </div>
      )
    }
  }
}

// 校验规则
const nameRule = {
  required: true,
  message: 'please input your name'
}
const passwordRule = {
  required: true,
  message: 'please input your password'
}


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

Dialog组件(弹窗组件)

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(){
    window.document.body.removeChild(this.node)
  }
  render() {
    return createPortal(
      <div className="dialog">
        <h1>Dialog</h1>
      </div>,
      this.node
    )
  }
}

DialogPage.js

import React, { Component } from 'react'
import {Button} from 'antd';
import Dialog from '../components/Dialog';

export default class DialogPage extends Component {
  constructor(props){
    super(props)
    this.state = {
      showDialog: false
    }
  }
  handleShowDialog = ()=>{
    this.setState({
      showDialog: !this.state.showDialog
    })
  }
  render() {
    const {showDialog} = this.state
    return (
      <div>
        <h1>DialogPage</h1>
        <Button onClick={this.handleShowDialog}>dialog toggle</Button>
        {
          showDialog && <Dialog />
        }
      </div>
    )
  }
}

树形组件

css文件

.tri {
  width: 20px;
  height: 20px;
  margin-right: 2px;
  padding-right: 4px;
}

.tri-close:after,
.tri-open:after {
  content: "";
  display: inline-block;
  width: 0;
  height: 0;
  border-top: 6px solid transparent;
  border-left: 8px solid black;
  border-bottom: 6px solid transparent;
}

.tri-open:after {
  transform: rotate(90deg);
}

TreeNode.js

import React, { Component } from 'react'
import classnames from 'classnames';

export default class TreeNode extends Component {
  constructor(props){
    super(props)
    this.state = {
      expanded: false
    }
  }
  handleExpanded = ()=>{
    this.setState({
      expanded: !this.state.expanded
    })
  }
  render() {
    const {title, children} = this.props.data
    const {expanded} = this.state
    const hasChildren = children && children.length > 0
    return (
      <div>
        <div className="nodesInner" onClick={this.handleExpanded}>
          {
            hasChildren && 
            <i className={classnames("tri", expanded ? 'tri-open':'tri-close')}></i>
          }
          <span>{title}</span>
        </div>
        {
          hasChildren && expanded &&
          <div className="children">
            {
              children.map(item=>{
                return <TreeNode key={item.key} data={item}/>
              })
            }
          </div>
        }
      </div>
      
    )
  }
}

TreePage.js

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}/>
      </div>
    )
  }
}

常见组件优化技术

定制组件的 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(item=>{
            return <Comment key={item.author} data={item}/>
          })
        }
      </div>
    )
  }
}

class Comment extends Component{
  shouldComponentUpdate(nextProps, nextState){
    const {author, body} = nextProps
    const {author: nowAuthor, body: nowBody} = nextProps
    if(author===nowAuthor && body === nowBody) {
      return false
    }
  }
  render(){
    const {author, body} = this.props.data
    console.log('render');
    return <div className="border">
      <p>作者: {author}</p>
      <p>内容: {body}</p>
    </div>
  }
}

PureComponent

import React, { Component, PureComponent } from 'react'

export default class PureConponentPage extends Component {
  constructor(props){
    super(props)
    this.state = {
      counter: 0
    }
  }
  setCounter = ()=>{
    this.setState({
      counter: 1
    })
  }
  render() {
    const {counter} = this.state
    return (
      <div>
        <h1>PureConponentPage</h1>
        <button onClick={this.setCounter}>change</button>
        <Demo counter={counter}/>
      </div>
    )
  }
}

class Demo extends PureComponent{
  render(){
    const {counter} = this.props
    console.log('render');
    return <div>
      {counter}
    </div>
  }
}

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