Рекомендации по совместному использованию кода React

React.js
Рекомендации по совместному использованию кода React

Когда любой проект развивается до определенной сложности, он неизбежно сталкивается с проблемой повторного использования логики. существуетReactОбычно существуют следующие способы реализации логического мультиплексирования:Mixin,高阶组件(HOC),修饰器(decorator),Render Props,Hook. В этой статье в основном анализируются преимущества и недостатки вышеуказанных методов, чтобы помочь разработчикам сделать методы более подходящими для бизнес-сценариев.

Mixin

Может быть, это просто отVueпеременаReactПервый способ, который могут придумать разработчики.MixinОн широко используется в различных объектно-ориентированных языках,Его роль заключается в создании эффекта, аналогичного множественному наследованию для языков с одинарным наследованием.. Хотя сейчасReactбыл заброшен, ноMixinдействительно былReactШаблон проектирования для реализации совместного использования кода.

Обобщенный метод примеси заключается в монтировании методов объекта примеси к исходному объекту путем присваивания для реализации смешивания объектов, подобно функции Object.assign() в ES6. Принцип заключается в следующем:

const mixin = function (obj, mixins) {
  const newObj = obj
  newObj.prototype = Object.create(obj.prototype)

  for (let prop in mixins) {
    // 遍历mixins的属性
    if (mixins.hasOwnPrototype(prop)) {
      // 判断是否为mixin的自身属性
      newObj.prototype[prop] = mixins[prop]; // 赋值
    }
  }
  return newObj
};

Использование миксинов в React

Предположим, в нашем проекте нескольким компонентам необходимо установить значение по умолчанию.nameсвойства, использованиеmixinЭто может избавить нас от необходимости писать несколько одинаковых компонентов в разных компонентах.getDefaultPropsметод, мы можем определитьmixin:

const DefaultNameMixin = {
  getDefaultProps: function () {
    return {
      name: "Joy"
    }
  }
}

чтобы использоватьmixin, который необходимо добавить в компонентmixinsсвойства, а затем пишемmixinоберните его в массив, используйте его какmixinsСтоимость свойства:

const ComponentOne = React.createClass({
  mixins: [DefaultNameMixin]
  render: function () {
    return <h2>Hello {this.props.name}</h2>
  }
})

написаноmixinМожет быть повторно использован в других компонентах.

из-заmixinsЗначение свойства представляет собой массив, то есть мыВ одном компоненте можно сделать несколько вызововmixin. Небольшое изменение в приведенном выше примере дает:

const DefaultFriendMixin = {
  getDefaultProps: function () {
    return {
      friend: "Yummy"
    }
  }
}

const ComponentOne = React.createClass({
  mixins: [DefaultNameMixin, DefaultFriendMixin]
  render: function () {
    return (
      <div>
        <h2>Hello {this.props.name}</h2>
        <h2>This is my friend {this.props.friend}</h2>
      </div>
    )
  }
})

мы можем дажеmixinсодержит другиеmixin.

например написать новыйmixin``DefaultPropsв том числе вышеDefaultNameMixinа такжеDefaultFriendMixin :

const DefaultPropsMixin = {
  mixins: [DefaultNameMixin, DefaultFriendMixin]
}

const ComponentOne = React.createClass({
  mixins: [DefaultPropsMixin]
  render: function () {
    return (
      <div>
        <h2>Hello {this.props.name}</h2>
        <h2>This is my friend {this.props.friend}</h2>
      </div>
    )
  }
})

Пока что мы можем сделать вывод, чтоmixinКак минимум иметь следующие преимущества:

  • Вы можете использовать одно и то же в нескольких компонентахmixin;
  • Можно использовать несколько в одном компонентеmixin;
  • может быть в том жеmixinнесколько вложенныхmixin;

Но в разных сценариях преимущества могут также превратиться в недостатки:

  • Уничтожьте пакет исходного компонента, возможно, потребуется сохранить новыйstateа такжеpropsсостояние ожидания;
  • разныеmixinИменование является агностическим и очень склонным к конфликту;
  • Может вызывать проблемы с рекурсивными вызовами, увеличивая сложность проекта и сложность обслуживания.;

Помимо,mixinОн имеет собственную логику обработки для таких проблем, как конфликты состояний, конфликты методов и последовательность вызовов нескольких методов жизненного цикла. Заинтересованные студенты могут обратиться к следующим статьям:

компоненты более высокого порядка

из-заmixinВышеупомянутые дефекты существуют, поэтомуReactраздетыйmixin, используйте вместо高阶组件заменить его.

高阶组件По сути, это функция, которая принимает компонент в качестве параметра и возвращает новый компонент..

ReactЧиновники также используют его при реализации некоторых публичных компонентов.高阶组件,Напримерreact-routerсерединаwithRouter,так же какReduxсерединаconnect. здесь сwithRouterНапример.

По умолчанию он должен бытьRouteСуществуют только компоненты, маршруты которых соответствуют рендерингу.this.props, иметь только路由参数, использовать函数式导航выполнение письмаthis.props.history.push('/next')Перейти на страницу соответствующего маршрута.高阶组件серединаwithRouterЭффект заключается в преобразованииRouteНаправьте обернутый компонент, обернув егоRouteвнутрь, чтобыreact-routerтри объектаhistory,location,matchв компонентpropsсвойства, так что это может быть достигнуто函数式导航跳转.

withRouterПринцип реализации:

const withRouter = (Component) => {
  const displayName = `withRouter(${Component.displayName || Component.name})`
  const C = props => {
    const { wrappedComponentRef, ...remainingProps } = props
    return (
      <RouterContext.Consumer>
        {context => {
          invariant(
            context,
            `You should not use <${displayName} /> outside a <Router>`
          );
          return (
            <Component
              {...remainingProps}
              {...context}
              ref={wrappedComponentRef}
            />
          )
        }}
      </RouterContext.Consumer>
    )
}

Используйте код:

import React, { Component } from "react"
import { withRouter } from "react-router"
class TopHeader extends Component {
  render() {
    return (
      <div>
        导航栏
        {/* 点击跳转login */}
        <button onClick={this.exit}>退出</button>
      </div>
    )
  }

  exit = () => {
    // 经过withRouter高阶函数包裹,就可以使用this.props进行跳转操作
    this.props.history.push("/login")
  }
}
// 使用withRouter包裹组件,返回history,location等
export default withRouter(TopHeader)

из-за高阶组件Суть в том获取组件并且返回新组件的方法, поэтому теоретически это также может быть похоже наmixinТа же реализация множественной вложенности.

Например:

Напишите функцию высшего порядка, которая позволяет петь

import React, { Component } from 'react'

const widthSinging = WrappedComponent => {
	return class HOC extends Component {
		constructor () {
			super(...arguments)
			this.singing = this.singing.bind(this)
		}

		singing = () => {
			console.log('i am singing!')
		}

		render() {
			return <WrappedComponent />
		}
	}
}

Напишите функцию высшего порядка, которая позволяет танцевать

import React, { Component } from 'react'

const widthDancing = WrappedComponent => {
	return class HOC extends Component {
		constructor () {
			super(...arguments)
			this.dancing = this.dancing.bind(this)
		}

		dancing = () => {
			console.log('i am dancing!')
		}

		render() {
			return <WrappedComponent />
		}
	}
}

Используйте вышеуказанные компоненты более высокого порядка

import React, { Component } from "react"
import { widthSing, widthDancing } from "hocs"

class Joy extends Component {
  render() {
    return <div>Joy</div>
  }
}

// 给Joy赋能唱歌和跳舞的特长
export default widthSinging(withDancing(Joy))

Из вышеизложенного видно, что просто используя функции высокого порядка для простой упаковки, оригинальную Джой можно превратить в маленького принца ночного клуба, который может и петь, и танцевать!

Конвенции для использования HOC

В использованииHOCВ то время существуют некоторые жесткие соглашения:

  • Передайте несвязанные реквизиты компоненту-оболочке (передайте реквизиты, не связанные с их конкретным содержимым);
  • Пошаговая композиция (избегая объединения разных форм вызовов HOC);
  • Включите отображаемое отображаемое имя для облегчения отладки (каждый HOC должен соответствовать обычному отображаемому имени);
  • УходитеrenderИспользуйте компоненты более высокого порядка в функции (каждый рендеринг более высокого порядка возвращает новый компонент, который влияет на производительность diff);
  • Статические методы должны быть скопированы (новый компонент, возвращаемый более высоким порядком, не содержит статических методов исходного компонента);
  • Избегайте использования ref (ref не будет передан);

Преимущества и недостатки ХОС

Пока что мы можем подвести итог高阶组件(HOC)Преимущества:

  • HOCэто чистая функция, простая в использовании и обслуживании;
  • также из-заHOCЭто чистая функция, которая поддерживает передачу нескольких параметров для расширения области применения;
  • HOCТо, что возвращается, является компонентом, который можно комбинировать и вкладывать, и он обладает высокой гибкостью;

КонечноHOCЕсть и некоторые проблемы:

  • когда несколькоHOCПри вложенности невозможно напрямую судить о подкомпоненте.propsиз которогоHOCответственный за доставку;
  • Когда родительский и дочерний компоненты имеют одинаковое имяprops, приведет к тому, что родительский компонент переопределит дочерний компонент с тем же именемpropsпроблема, иreactОб ошибках не сообщается, а восприятие разработчика низкое;
  • КаждыйHOCВсе возвращают новый компонент, что приводит к большому количеству бесполезных компонентов и в то же время углубляет иерархию компонентов, что затрудняет устранение неполадок;

修饰器а также高阶组件Он относится к той же модели и здесь обсуждаться не будет.

Render Props

Render PropsЭто очень гибкий и повторно используемый шаблон, который может инкапсулировать определенное поведение или функцию в компонент и предоставлять его другим компонентам, чтобы позволить другим компонентам иметь такие возможности..

Термин «реквизит рендеринга» относится к методу совместного использования кода между компонентами React с использованием реквизита, значением которого является функция.

ЭтоReactофициальный дляRender PropsОпределение , переведенное на местный язык, звучит так: «Render PropsреализуетсяReact ComponentsТехника совместного использования кода между компонентами,propsсодержитfunctionСвойство типа, которое может вызывать компонентpropsсвойства для реализации внутренней логики рендеринга компонента».

Официальный пример:

<DataProvider render={(data) => <h1>Hello {data.target}</h1>} />

Как указано выше,DataProviderКомпонент имеетrender(можно называть другими именами)propsсвойство, свойство является функцией, и эта функция возвращаетReact Element, вызывая эту функцию внутри компонента для завершения рендеринга, то этот компонент используетсяrender propsТехнология.

Читатель может задаться вопросом: «Зачем нам нужно вызыватьpropsсвойств для достижения внутреннего рендеринга компонента без рендеринга непосредственно в компоненте"?Reactофициальный ответ,render propsне каждыйReactНавыки, которыми должны овладеть разработчики, и даже вы, возможно, никогда не воспользуетесь этим методом, но его существование дает разработчикам еще один выбор, когда они думают о совместном использовании кода компонентов.

Render Propsсцены, которые будут использоваться

Нам может понадобиться часто использовать всплывающие окна при разработке проекта. Пользовательский интерфейс всплывающих окон может постоянно меняться, но функции схожи, а именно打开а также关闭. кantdНапример:

import { Modal, Button } from "antd"
class App extends React.Component {
  state = { visible: false }

  // 控制弹窗显示隐藏
  toggleModal = (visible) => {
    this.setState({ visible })
  };

  handleOk = (e) => {
    // 做点什么
    this.setState({ visible: false })
  }

  render() {
    const { visible } = this.state
    return (
      <div>
        <Button onClick={this.toggleModal.bind(this, true)}>Open</Button>
        <Modal
          title="Basic Modal"
          visible={visible}
          onOk={this.handleOk}
          onCancel={this.toggleModal.bind(this, false)}
        >
          <p>Some contents...</p>
        </Modal>
      </div>
    )
  }
}

Выше самое простоеModelИспользуя пример, даже если это простое использование, нам все равно нужно обратить внимание на его состояние отображения и реализовать его метод переключения. Но на самом деле разработчики хотят сосредоточиться только на бизнес-логике, связанной сonOk, идеальное использование должно быть таким:

<MyModal>
  <Button>Open</Button>
  <Modal title="Basic Modal" onOk={this.handleOk}>
    <p>Some contents...</p>
  </Modal>
</MyModal>

в состоянии пройтиrender propsДля достижения вышеуказанного использования:

import { Modal, Button } from "antd"
class MyModal extends React.Component {
  state = { on: false }

  toggle = () => {
    this.setState({
      on: !this.state.on
    })
  }

  renderButton = (props) => <Button {...props} onClick={this.toggle} />

  renderModal = ({ onOK, ...rest }) => (
    <Modal
      {...rest}
      visible={this.state.on}
      onOk={() => {
        onOK && onOK()
        this.toggle()
      }}
      onCancel={this.toggle}
    />
  )

  render() {
    return this.props.children({
      Button: this.renderButton,
      Modal: this.renderModal
    })
  }
}

Таким образом, мы завершили состояние и основную функциюModal, мы используем это на других страницахModal, вам нужно только сосредоточиться на конкретной бизнес-логике.

Как видно из вышеизложенного,render propsнастоящийReactкомпонент вместо чего-то вродеHOCтакой же только одинФункция, которая может возвращать компонент, что также означает использованиеrender propsне какHOCТа же проблема вложенности на уровне компонентов, не беспокойтесь об этомpropsУстранение проблем, возникающих из-за конфликтов имен.

render propsограничения на использование

существуетrender propsследует избегать в箭头函数, так как это влияет на производительность.

Например:

// 不好的示例
class MouseTracker extends React.Component {
  render() {
    return (
      <Mouse render={mouse => (
        <Cat mouse={mouse} />
      )}/>
    )
  }
}

Плохо так писать, потому чтоrenderметод можно отображать несколько раз, используйте箭头函数, из-за чего каждый раз при рендеринге будет передаватьсяrenderЗначение будет отличаться, когда на самом деле нет никакой разницы, что может привести к вопросам производительности.

Так что лучший способ написать это было бы передатьrenderФункции в определены как методы экземпляра, так что даже если мы визуализируем несколько раз, привязка всегда будет одной и той же функцией.

// 好的示例
class MouseTracker extends React.Component {
  renderCat(mouse) {
  	return <Cat mouse={mouse} />
  }

  render() {
    return (
		  <Mouse render={this.renderTheCat} />
    )
  }
}

render propsПреимущества и недостатки

  • преимущество

    • Имя реквизита можно изменить, взаимного переопределения нет;
    • понятный источник реквизита;
    • Не будет многоуровневой вложенности компонентов;
  • недостаток

    • сложно писать;

    • невозможноreturnдоступ к данным вне оператора;

    • Легко сгенерировать вложенность обратного вызова функции;

      Следующий код:

      const MyComponent = () => {
        return (
          <Mouse>
            {({ x, y }) => (
              <Page>
                {({ x: pageX, y: pageY }) => (
                  <Connection>
                    {({ api }) => {
                      // yikes
                    }}
                  </Connection>
                )}
              </Page>
            )}
          </Mouse>
        )
      }
      

Hook

ReactЯдром является составляющая, следовательно,ReactВсегда работайте над оптимизацией и совершенствованием способа объявления компонентов. с самого раннего类组件, затем к函数组件, у каждого есть преимущества и недостатки.类组件Он может предоставить нам полный жизненный цикл и состояние (состояние), но очень громоздок в написании, и函数组件Несмотря на то, что он очень краткий и легкий, его ограниченияДолжна быть чистой функцией, не может содержать состояние и не поддерживает жизненные циклы.,следовательно类组件не заменяет函数组件.

а такжеReactкоманда чувствуетКомпоненты лучше всего писать как функции, а не классы, в результате чегоReact Hooks.

Целью разработки React Hooks является улучшение функциональных компонентов, и вы можете написать полнофункциональный компонент, вообще не используя «классы»..

зачем говорить类组件«Громоздкий», чтобы позаимствоватьReactОфициальный пример гласит:

import React, { Component } from "react"

export default class Button extends Component {
  constructor() {
    super()
    this.state = { buttonText: "Click me, please" }
    this.handleClick = this.handleClick.bind(this)
  }
  handleClick() {
    this.setState(() => {
      return { buttonText: "Thanks, been clicked!" }
    })
  }
  render() {
    const { buttonText } = this.state
    return <button onClick={this.handleClick}>{buttonText}</button>
  }
}

Выше показан простой компонент кнопки, включая самое основное состояние и метод щелчка.После нажатия кнопки состояние изменяется.

Это очень простой функциональный компонент, но для его реализации требуется много кода. из-за函数组件не содержит состояния, поэтому мы не можем использовать函数组件объявить компонент с вышеуказанными функциями. Но мы можем использоватьHookреализовать:

import React, { useState } from "react"

export default function Button() {
  const [buttonText, setButtonText] = useState("Click me,   please")

  function handleClick() {
    return setButtonText("Thanks, been clicked!")
  }

  return <button onClick={handleClick}>{buttonText}</button>
}

В сравнении,HookКажется светлее в непосредственной близости函数组件При этом он сохраняет собственное состояние.

Первый хук представлен в приведенном выше примере.useState(),Помимо,ReactЧиновник также предоставилuseEffect(),useContext(),useReducer()Подождите крючок. Конкретные хуки и подробности их использования см.официальный.

HookГибкость также ложь, в дополнение к официальным базовым крючкам, мы также можем использовать эту базу пакета и пользовательский крюк к крючкому, что обеспечивает более простое повторное использование кода.

КРЮК выступает

  • преимущество
    • Легче повторно использовать код;
    • Чистый стиль кода;
    • меньше кода;
  • недостаток
    • Асинхронное состояние (функции выполняются независимо, каждая функция имеет отдельную область видимости)
    • нужно более рациональное использованиеuseEffect
    • Степень детализации мала, и необходимо абстрагироваться от многих сложных логических элементов.hook

Суммировать

КромеMixinПомимо того, что он немного отстает из-за собственных очевидных недостатков, для高阶组件,render props,react hook, это никак нельзя назвать最佳方案, все они имеют преимущества и недостатки. даже самый популярныйreact hook, хотя каждыйhookЭто выглядит таким коротким и освежающим, но в реальном бизнесе обычно одна бизнес-функция соответствует несколькимhook, а это значит, что при изменении бизнеса необходимо поддерживать несколькоhookизменения, а не сохранениеclassДругими словами, умственная нагрузка может сильно увеличиться. Только так, как подходит вашему бизнесу最佳方案.


Справочная документация: