0

0

0

修罗

站点介绍

只有了解事实才能获得真正的自由

React组件化

修罗 2020-11-21 1729 0条评论 react

首页 / 正文

React组件化

目标

掌握组件化开发中多种实现技术

  1. 掌握高阶组件HOC。
  2. 了解组件化概念,能设计并实现自己需要的组件 。
  3. 掌握弹窗类实现,掌握传送门使用。

资源

create-react-app: https://www.html.cn/create-react-app/docs/getting-started/

HOC:https://reactjs.org/docs/higher-order-components.html

ant design:https://ant.design/docs/react/use-with-create-react-app-cn

组件化优点

  1. 增强代码重用性,提高开发效率
  2. 简化调试步骤,提升整个项目的可维护性
  3. 便于协同开发
  4. 注意点:降低耦合性

一、高阶组件-HOC

为了提高组件复用率,可测试性,就要保证组件功能单一性;但是若要满足复杂需求就要扩展功能单一的组件,在React里就有了 HOC(Higher-Order Components)的概念。

定义:高阶组件是参数为组件,返回值为新组件的函数。

基本使用

1、高阶组件定义

// hoc: 是一个函数,接收一个组件,返回另外一个组件
// 这里大写开头的Cmp是指function或者class组件
const foo = (Cmp) => (props) => {
  return (
    <div className="border">
      <Cmp {...props} />
    </div>
  );
};

2、定义组件,把该组件传递给高阶组件

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

const Foo = foo(Child);

3、使用Foo

export default class HocPage extends Component {
  render() {
    return (
      <div>
        <h3>HocPage</h3>
        <Foo name="msg" />
      </div>
    );
  }
}

链式调用

// 高阶组件foo
const foo = (Cmp) => (props) => {
  return (
    <div className="border">
      <Cmp {...props} />
    </div>
  );
};

// 高阶组件foo2
const foo2 = (Cmp) => (props) => {
  return (
    <div className="greenBorder">
      <Cmp {...props} />
    </div>
  );
};

// 传给高阶组件的普通函数组件
function Child(props) {
  return <div> Child {props.name}</div>;
}

// 链式调用
const Foo = foo2(foo(foo(Child)));

// 显示
export default class HocPage extends Component {
  render() {
    return (
      <div>
        <h3>HocPage</h3>
        <Foo name="msg" />
      </div>
    );
  }
}

装饰器写法

高阶组件本身是对装饰器模式的应用,自然可以利用ES7中出现的装饰器语法来更优雅的书写代码。

npm install -D @babel/plugin-proposal-decorators

更新config-overrides.js

//配置完成后记得重启下
const { addDecoratorsLegacy } = require("customizecra");
module.exports = override(
 ...,
 addDecoratorsLegacy()//配置装饰器
);

1605888005245.png

如果vscode对装饰器有warning,vscode设置加上

javascript.implicitProjectConfig.experimentalDecorator
s": true

装饰器使用

//HocPage.js
//...
// !装饰器只能用在class上
// 执行顺序从下往上
@foo2
@foo
@foo
class Child extends Component {
  render() {
    return <div> Child {this.props.name}</div>;
  }
}

// const Foo = foo2(foo(foo(Child)));
export default class HocPage extends Component {
  render() {
    return (
      <div>
        <h3>HocPage</h3>
        {/* <Foo name="msg" /> */}
        <Child />
      </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 />;
}

这不仅仅是性能问题 - 重新挂载组件会导致该组件及其所有子组件的状态丢失

二、表单组件设计

表单组件设计思路

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

表单组件实现

表单基本结构

创建MyFormPage.js

import React, {Component} from "react";
import kFormCreate from "../components/kFormCreate";

const nameRules = {required: true, message: "please input ur name"};
const passwordRules = {required: true, message: "please input ur password"};

@kFormCreate
class MyFormPage extends Component {
  submit = () => {
    const {getFieldsValue, getFieldValue, validateFields} = this.props;
    validateFields((err, values) => {
      if (err) {
        console.log("err", err); //sy-log
      } else {
        console.log("success", values); //sy-log
      }
    });
    console.log("submit", getFieldsValue(), getFieldValue("password"));
  };
  render() {
    console.log("props", this.props); //sy-log
    const {getFieldDecorator} = this.props;
    return (
      <div>
        <h3>MyFormPage</h3>
        {getFieldDecorator("name", {rules: [nameRules]})(
          <input type="text" placeholder="please input ur name" />
        )}
        {getFieldDecorator("password", {rules: [passwordRules]})(
          <input type="password" placeholder="please input ur password" />
        )}
        <button onClick={this.submit}>提交</button>
      </div>
    );
  }
}

export default MyFormPage;

高阶组件kFormCreate

扩展现有表单,./components/kFormCreate.js

import React, { Component } from "react";

export default function kFormCreate(Cmp) {
  return class extends Component {
    constructor(props) {
      super(props);
      this.state = { errors: {} };
      this.options = {};
    }
      
    // 输入框输入事件处理
    handleChange = (e) => {
      // setState name value
      let { name, value } = e.target;

      this.validate({
        ...this.state,
        [name]: value,
      });
      // this.setState({[name]: value}, () => {
      //   this.validate();
      // });
    };
    
    // 给表单项包装
    getFieldDecorator = (field, option) => {
      // options: { name: {rules: [{required: true, message: "please input ur name"}]} }
      this.options[field] = option;
      return (InputCmp) => {
        // 克隆一份
        return (
          <div>
            {React.cloneElement(InputCmp, {
              name: field,
              value: this.state[field] || "",
              onChange: this.handleChange,
            })}
            <p className="red">{this.state.errors[field]}</p>
          </div>
        );
      };
    };

    // 获取全部表单输入值
    getFieldsValue = () => {
      return { ...this.state };
    };

    // 根据表单name获取值
    getFieldValue = (field) => {
      return this.state[field];
    };

    // 表单校验
    validate = (state) => {
      const errors = {};
      // const state = {...this.state};
      for (let name in this.options) {
        if (state[name] === undefined) {
          // 没有输入,判断为不合法
          errors[name] = this.options[name].rules[0].message; //"error";
        }
      }
      this.setState({ ...state, errors });
    };

    // 表单提交调用,被包装的组件通过this.props.validateFields调用
    validateFields = (callback) => {
      // 校验错误信息
      // const errors = {};
      const state = { ...this.state };
      // for (let name in this.options) {
      //   if (state[name] === undefined) {
      //     // 没有输入,判断为不合法
      //     errors[name] = this.options[name].rules[0].message; //"error";
      //   }
      // }
      // this.setState({errors});
      this.validate(state);
      const { errors } = this.state;
      if (JSON.stringify(errors) === "{}") {
        // 合法
        callback(undefined, state);
      } else {
        callback(errors, state);
      }
    };

    render() {
      return (
        <div className="border">
          <Cmp
            getFieldDecorator={this.getFieldDecorator}
            getFieldsValue={this.getFieldsValue}
            getFieldValue={this.getFieldValue}
            validateFields={this.validateFields}
          />
        </div>
      );
    }
  };
}

二、弹窗类组件设计与实现

设计思路

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

具体实现: Portal

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

范例:Dialog组件

import React, {Component} from "react";
import {createPortal} from "react-dom";

export default class Dialog extends Component {
  constructor(props) {
    super(props);
    // 创建div并追加到body下
    const doc = window.document;
    this.node = doc.createElement("div");
    doc.body.appendChild(this.node);
  }

  componentWillUnmount() {
    // 组件销毁前移除div
    window.document.body.removeChild(this.node);
  }

  render() {
    // 将类名为dialog的元素渲染到之前construct里创建的div
    return createPortal(
      <div className="dialog">
        <h3>Dialog</h3>
        {this.props.children}
      </div>,
      this.node
    );
  }
}
.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;
}

总结:

  1. Dialog什么都不给自己画,render返回个null就够了;
  2. 它做得事情是通过调用createPortal把要画的东西画在DOM树上另一个⻆落。

评论(0)


最新评论

  • 1

    1

  • 1

    1

  • -1' OR 2+158-158-1=0+0+0+1 or 'TKCTZnRa'='

    1

  • 1

    1

  • 1

    1

  • 1

    1

  • 1

    1

  • @@5Qa2D

    1

  • 1

    1

  • 1

    1

日历

2025年09月

 123456
78910111213
14151617181920
21222324252627
282930    

文章目录

推荐关键字: Linux webpack js 算法 MongoDB laravel JAVA jquery javase redis