React专题—组件封装

487 阅读3分钟

示例场景:获取鼠标当前的位置

一、HOC(高阶组件)

示例:

// MousePosition.js HOC封装函数
import React, { Component }  from 'react';
export default WrappedComponent => {
  return class extends Component {
    constructor(props){
      super(props)
      this.state = {
        positionX: 0,
        positionY: 0
      }
    }
    componentDidMount() {
      document.addEventListener('mousemove', (e) => {
        this.setState({
          positionX: e.clientX,
          positionY: e.clientY
        });
      })
    }
    render(){
      return <WrappedComponent {...this.props} {...this.state} />;
    }
  }
}

// MousePoint.js 位置信息展示
import React, { Component } from 'react';
import mousePositionHoc from './hoc/MousePosition';
class MousePoint extends Component{
  constructor(props){
    super(props);
  }
  render(){
    return(
      <div>
        <span>鼠标的横坐标{this.props.positionX}</span>
        <span>鼠标的纵坐标{this.props.positionY}</span>
      </div>
        )
    }
}
export default mousePositionHoc(MousePoint)

二、Render Props

示例:

// MousePoint.js
import React, { Component  } from 'react';
export default class MousePoint extends Component {
  constructor(props) {
    super(props);
    this.state = {
      positionX: 0,
      positionY: 0
    };
  }
  componentDidMount() {
    document.addEventListener('mousemove', (e) => {
      this.setState({
        positionX: e.clientX,
        positionY: e.clientY
      });
    })
  }

  render() {
    return (
      <div>{this.props.render(this.state)}</div>
        );
    }
}

// 父组件
import React, { Component } from 'react';
import MousePoint from './MousePoint';
export default class MouseTracker extends Component{
  constructor(props){
    super(props);
  }
  render(){
    return(
      <div>
        <MousePoint
          render={(state) => {
            return(
              <div>
                <span>鼠标横坐标是{state.positionX}</span>
                <span>鼠标纵坐标是{state.positionY}</span>
              </div>
            )
          }}
        />
            </div>
        )
    }
}

三、children

示例:

import React, { Component } from 'react';
export default class ChildComponent extends Component{
  constructor(props){
    super(props);
  }
  render(){
    return(
      <div>{this.props.children}</div>
    )
  }
}
function App() {
  return (
    <div className="App">
      <ChildComponent>
        <p>Hello World</p>
      </ChildComponent>
    </div>
  );
}

四、Hook

示例:

// useMousePosition.js Hook
import React, { useState, useEffect } from 'react';
export default () => {
  const [positionX, setPositionX] = useState(0);
  const [positionY, setPositionY] = useState(0);
  const getMousePosition = (e) => {
    setPositionX(e.clientX);
    setPositionY(e.clientY);
  };
  useEffect(() => {
    document.addEventListener('mousemove', getMousePosition);
    return () => {
      document.removeEventListener('mousemove', getMousePosition);
    };
  });
  return {
    positionX: positionX,
    positionY: positionY
  }
}

// 使用
import React, { useState, useEffect } from 'react';
import useMousePosition from './useMousePosition';
export default () => {
  const mousePosition = useMousePosition();
  return(
    <div>
      <span>鼠标的横坐标{mousePosition.positionX}</span>
      <span>鼠标的纵坐标{mousePosition.positionY}</span>
    </div>
)

五、注意事项

1、父组件不能使用props传输整个组件实例或refs给子组件,这样会破坏封装

示例:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { number: 0 };
  }
  render() {
    return (
      <div className="app">
        <span className="number">{this.state.number}</span>
            <Controls parent={this} />
      </div>
        );
    }
}

class Controls extends React.Component {
  render() {
    return (
      <div className="controls">
        <button onClick={() => this.updateNumber(+1)}>Increase</button>
            <button onClick={() => this.updateNumber(-1)}>Decrease</button>
      </div>
        );
}

updateNumber(toAdd) {
  this.props.parent.setState(prevState => ({
    number: prevState.number + toAdd
  }));
}
}

示例优化:

class App extends Component {
  constructor(props) {
    super(props);
    this.state = { number: 0 };
  }
  render() {
    return (
      <div className="app">
        <span className="number">{this.state.number}</span>
        <Controls
          onIncrease={() => this.updateNumber(+1)}
          onDecrease={() => this.updateNumber(-1)}
        />
            </div>
        );
    }
  updateNumber(toAdd) {
    this.setState(prevState => ({
      number: prevState.number + toAdd
    }));
  }
}

const Controls = ({ onIncrease, onDecrease }) => {
  return (
    <div className="controls">
      <button onClick={onIncrease}>Increase</button>
      <button onClick={onDecrease}>Decrease</button>
    </div>
    );
};

2、容器组件负责状态的管理和数据的请求,UI组件负责页面的渲染

示例:

// 容器组件 负责状态的管理和数据的请求
import axios from 'axios';
class WeatherFetch extends Component {
  constructor(props) {
    super(props);
    this.state = { temperature: 'N/A', windSpeed: 'N/A' };
  }
  render() {
    const { temperature, windSpeed } = this.state;
    return (
      <WeatherInfo temperature={temperature} windSpeed={windSpeed} />
    );
    }
  async componentDidMount() {
    const response = await axios.get('http://weather.com/api');
    const { current } = response.data;
    this.setState({
      temperature: current.temperature,
      windSpeed: current.windSpeed
    });
  }
}

// UI组件 负责页面的渲染
function WeatherInfo({ temperature, windSpeed }) {
  return (
    <div className="weather">
      <div>Temperature: {temperature}°C</div>
      <div>Wind: {windSpeed} km/h</div>
    </div>
    );
}

3、HOC偏好单一责任原则

HOC的一个常见用法是为封装的组件增加新属性或修改现有的属性值。这种技术称为属性代理。

示例:

function withNewFunctionality(WrappedComponent) {
  return class NewFunctionality extends Component {
    render() {
      const newProp = 'Value';
      const propsProxy = {
        ...this.props,
        // 修改现有属性:
        ownProp: this.props.ownProp + ' was modified',
        // 增加新属性:
        newProp
      };
      return <WrappedComponent {...propsProxy} />;
    }
  }
}
const MyNewComponent = withNewFunctionality(MyComponent);

你还可以通过控制输入组件的渲染过程从而控制渲染结果。这种 HOC 技术被称为渲染劫持。

示例:

function withModifiedChildren(WrappedComponent) {
  return class ModifiedChildren extends WrappedComponent {
    render() {
      const rootElement = super.render();
      const newChildren = [
        ...rootElement.props.children,
        // 插入一个元素
        <div>New child</div>
      ];
      return cloneElement(
        rootElement,
        rootElement.props,
        newChildren
      );
    }
  }
}
const MyNewComponent = withModifiedChildren(MyComponent);

你还可以通过HOC的属性代理技术实现职责的分离

示例:

// 展示表单字段和附加的事件处理程序由PersistentForm承担
class PersistentForm extends Component {
  constructor(props) {
    super(props);
    this.state = { inputValue: props.initialValue };
    this.handleChange = this.handleChange.bind(this);
    this.handleClick = this.handleClick.bind(this);
  }
  render() {
    const { inputValue } = this.state;
    return (
      <div className="persistent-form">
        <input type="text" value={inputValue} onChange={this.handleChange} />
            <button onClick={this.handleClick}>Save to storage</button>
      </div>
    );
    }
  handleChange(event) {
    this.setState({
      inputValue: event.target.value
    });
  }
  handleClick() {
    this.props.saveValue(this.state.inputValue);
  }
}

// 查询和保存到本地存储的职责由 withPersistence()HOC承担
function withPersistence(storageKey, storage) {
  return function (WrappedComponent) {
    return class PersistentComponent extends Component {
      constructor(props) {
        super(props);
        this.state = { initialValue: storage.getItem(storageKey) };
      }
      render() {
        return (
          <WrappedComponent
            initialValue={this.state.initialValue}
            saveValue={this.saveValue}
            {...this.props}
          />
            );
        }
      saveValue(value) {
        storage.setItem(storageKey, value);
      }
        }
    }
}

// 使用
const LocalStoragePersistentForm = withPersistence('key', localStorage)(PersistentForm);
const instance = <LocalStoragePersistentForm />;
// 你可以轻松地将存储类型更改为session storage
const SessionStoragePersistentForm = withPersistence('key', sessionStorage)(PersistentForm);
const instance = <SessionStoragePersistentForm />;