3.React组件化2

课堂目标

  • 掌握第三⽅组件正确使⽤⽅式
  • 能设计并实现⾃⼰的组件
  • 了解常⻅组件优化技术

知识要点

  • 使⽤antd
  • 设计并实现表单控件
  • 实现弹窗类组件
  • 实现树组件
  • 使⽤PureComponent、memo

资源

快速开始

Create React App 中文文档

npx create-react-app my-app
cd my-app
npm start

使⽤第三⽅组件

不必eject,直接安装: npm install antd --save

范例:试⽤ ant-design组件库

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.confifig.js

npm install react-app-rewired customize-cra babel-plugin-import -D

根⽬录创建config-overrides.js

const { override, fixBabelImports } = require('customize-cra')
module.exports = override(
  fixBabelImports('import', {
    //antd按需加载
    libraryName: 'antd',
    libraryDirectory: 'es',
    style: 'css',
  })
)
//修改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
const { addDecoratorsLegacy } = require('customize-cra')
module.exports = override(
  //...,
  addDecoratorsLegacy() //配置装饰器
)

HocPage.js【按需加载和实现装饰器之后的⻚⾯如下】

import React, { Component } from 'react'
import { Button } from 'antd'
const foo = (Cmp) => (props) => {
  return (
    <div className="border">
      <Cmp {...props} />
    </div>
  )
}
const foo2 = (Cmp) => (props) => {
  return (
    <div className="border" style={{ border: 'solid 1px red' }}>
      <Cmp {...props} />
    </div>
  )
}
@foo
@foo2
class Child extends Component {
  render() {
    return <div className="border">child</div>
  }
}
/* function Child(props) {
 return <div className="border">child</div>;
} */
@foo2
class HocPage extends Component {
  render() {
    // const Foo = foo2(foo(Child));
    return (
      <div>
        <h1>HocPage</h1>
        <Child />
        <Button type="dashed">click</Button>
      </div>
    )
  }
}
export default HocPage

表单组件设计思路

表单组件设计思路

  • 表单组件要求实现数据收集、校验、提交等特性,可通过⾼阶组件扩展
  • ⾼阶组件给表单组件传递⼀个input组件包装函数接管其输⼊事件并统⼀管理表单数据
  • ⾼阶组件给表单组件传递⼀个校验函数使其具备数据校验功能

antd表单试⽤

import React, { Component } from 'react'
import { Form, Input, Icon, Button } from 'antd'
const FormItem = Form.Item
//校验规则
const nameRules = { required: true, message: 'please input your name' }
const passwordRules = { required: true, message: 'please input your password' }
@Form.create()
class FormPageDecorators extends Component {
  handleSubmit = () => {
    /* const { getFieldsValue, getFieldValue } = this.props.form;
 console.log("submit", getFieldsValue()); */
    const { validateFields } = this.props.form
    validateFields((err, values) => {
      if (err) {
        console.log('err', err)
      } else {
        console.log('submit', values)
      }
    })
  }
  render() {
    const { getFieldDecorator } = this.props.form
    // console.log(this.props.form);
    return (
      <div>
        <h1>FormPageDecorators</h1>
        <Form>
          <FormItem label="姓名">
            {getFieldDecorator('name', { rules: [nameRules] })(
              <Input prefix={<Icon type="user" />} />
            )}
          </FormItem>
          <FormItem label="密码">
            {getFieldDecorator('password', {
              rules: [passwordRules],
            })(
              <Input
                type="password"
                prefix={<Icon type="lock" />}
              />
            )}
          </FormItem>
          <FormItem label="姓名">
            <Button type="primary" onClick={this.handleSubmit}>
              提交
            </Button>
          </FormItem>
        </Form>
      </div>
    )
  }
}
export default FormPageDecorators
// export default Form.create()(FormPageDecorators);

表单组件-具体实现

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

import React, { Component } from 'react'
import kFormCreate from '../../components/kFormCreate'
const nameRules = { required: true, message: 'please input your name!' }
const passwordRules = {
  required: true,
  message: 'please input your password!',
}
class MyFormPage extends Component {
  handleSubmit = () => {
    const { getFieldValue } = this.props
    const res = {
      name: getFieldValue('name'),
      password: getFieldValue('password'),
    }
    console.log('hah', res)
  }
  handleSubmit2 = () => {
    // 加⼊校验
    const { validateFields } = this.props
    validateFields((err, values) => {
      if (err) {
        console.log('validateFields', err)
      } else {
        console.log('submit', values)
      }
    })
  }
  render() {
    const { getFieldDecorator } = this.props
    return (
      <div>
        <h1>MyFormPage</h1>
        <div>
          {getFieldDecorator('name', { rules: [nameRules] })(
            <input type="text" />
          )}
          {getFieldDecorator('password', [nameRules])(
            <input type="password" />
          )}
        </div>
        <button onClick={this.handleSubmit2}>submit</button>
      </div>
    )
  }
}
export default kFormCreate(MyFormPage)

⾼阶组件kFormCreate:扩展现有表单,./components/KFormTest.js

import React, { Component } from 'react'
export default function kFormCreate(Cmp) {
  return class extends Component {
    constructor(props) {
      super(props)
      this.options = {} //各字段选项
      this.state = {} //各字段值
    }
    handleChange = (e) => {
      let { name, value } = e.target
      this.setState({ [name]: value })
    }
    getFieldValue = (field) => {
      return this.state[field]
    }
    validateFields = (callback) => {
      const res = { ...this.state }
      const err = []
      for (let i in this.options) {
        if (res[i] === undefined) {
          err.push({ [i]: 'error' })
        }
      }
      if (err.length > 0) {
        callback(err, res)
      } else {
        callback(undefined, res)
      }
    }
    getFieldDecorator = (field, option) => {
      this.options[field] = option
      return (InputCmp) => (
        <div>
          {
            // 由React.createElement⽣成的元素不能修改,需要克隆⼀份再扩展
            React.cloneElement(InputCmp, {
              name: field,
              value: this.state[field] || '', //控件值
              onChange: this.handleChange, //控件change事件处理
            })
          }
        </div>
      )
    }
    render() {
      return (
        <div className="border">
          <Cmp
            {...this.props}
            getFieldDecorator={this.getFieldDecorator}
            getFieldValue={this.getFieldValue}
            validateFields={this.validateFields}
          />
        </div>
      )
    }
  }
}

antd表单试⽤

弹窗类组件-设计思路

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

// 常⻅⽤法如下:Dialog在当前组件声明,但是却在body中另⼀个div中显示
;<div class="foo">
  <div> ... </div>
  {needDialog && (
    <Dialog>
      <header>Any Header</header>
      <section>Any content</section>
    </Dialog>
  )}
</div>

弹窗类组件-具体实现-⽅案1:Portal

传送⻔,react v16之后出现的portal可以实现内容传送功能。

范例:Dialog组件

// Diallog.js
import React, { Component } from 'react'
import { createPortal } from 'react-dom'
import './index.scss'
export default class Diallog 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() {
    const { hideDialog } = this.props
    return createPortal(
      <div className="dialog">
        {this.props.children}
        {typeof hideDialog === 'function' && (
          <button onClick={hideDialog}>关掉弹窗</button>
        )}
      </div>,
      this.node
    )
  }
}

Diallog/index.scss

.dialog {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  line-height: 30px;
  width: 400px;
  height: 300px;
  transform: translate(50%, 50%);
  border: solid 1px gray;
  text-align: center; 
}

弹窗类组件-具体实现-⽅案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
    )
  }
}

总结⼀下

  • Dialog什么都不给⾃⼰画,render返回⼀个null就够了;
  • 它做得事情是通过调⽤createPortal把要画的东⻄画在DOM树上另⼀个⻆落。

树形组件-设计思路

递归:⾃⼰调⽤⾃⼰

如计算f(n)=f(n-1)*n; n>0, f(1)=1

function foo(n) {
  return n === 1 ? 1 : n * foo(n - 1)
}

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

树形组件-具体实现

TreeNode.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>
    )
  }
}

TreeNode.js

import React, { Component } from 'react'
import classnames from 'classnames' //先安装下npm install 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="nodeInner" onClick={this.handleExpanded}>
          {hasChildren && (
            <i
              className={classnames(
                'tri',
                expanded ? 'tri-open' : 'tri-close'
              )}
            ></i>
          )}
          <span>{title}</span>
          常⻅组件优化技术 定制组件的shouldComponentUpdate钩⼦
          范例:通过shouldComponentUpdate优化组件
        </div>
        {expanded && hasChildren && (
          <div className="children">
            {children.map((item) => {
              return <TreeNode key={item.key} data={item} />
            })}
          </div>
        )}
      </div>
    )
  }
}

树组件css 

.nodeInner {
  cursor: pointer;
}
.children {
  margin-left: 20px;
}
.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);
}

常⻅组件优化技术-定制组件的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

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

缺点是必须要⽤class形式,⽽且要注意是浅⽐较

常⻅组件优化技术-React.memo

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

import React, { Component, PureComponent, 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>
})

作业

实现下图:提示:可以使⽤antd的 Card、Input、Tree、Button、Form、Table

React组件化2

  • 课堂⽬标
  • 知识要点
  • 资源
  • 知识点
  • 快速开始
    • 使⽤第三⽅组件
    • 配置按需加载
  • 表单组件设计与实现
    • antd表单试⽤
    • 表单组件设计思路
    • 表单组件实现
  • 弹窗类组件设计与实现
    • 设计思路
    • 具体实现
      • ⽅案1:Portal
      • ⽅案2:unstable_renderSubtreeIntoContainer
  • 树形组件设计与实现
    • 设计思路
    • 实现
  • 常⻅组件优化技术
    • 定制组件的shouldComponentUpdate钩⼦
    • PureComponent
    • React.memo
  • 作业

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值