Наличие услуг в приложении React

177

Я из другого мира, где я мог бы извлечь логику в сервис / фабрику и использовать ее в своих контроллерах.

Я пытаюсь понять, как я могу добиться того же в приложении React.

Допустим, у меня есть компонент, который проверяет ввод пароля пользователя (это сила). Эта логика довольно сложна, поэтому я не хочу писать ее в компоненте, который она сама.

Где я должен написать эту логику? В магазине, если я использую флюс? Или есть лучший вариант?

Деннис Неруш
источник
Вы можете использовать пакет и посмотреть, как они это делают - npmjs.com/package/react-password-strength-meter
James111
11
Надежность пароля является лишь примером. Я ищу более общую лучшую практику
Деннис Неруш
Возможно, вам придется сделать это на стороне сервера?
James111
2
Нет. Только логика на стороне клиента, которая не должна быть непосредственно в компоненте.
Проверка надежности
4
Если у вас много таких функций, вы можете сохранить их в файле помощника и просто использовать его в файле компонента для использования. Если это отдельная функция, относящаяся исключительно к этому компоненту, она, вероятно, должна жить там, независимо от сложности.
Джесси Кернаган

Ответы:

61

Первый ответ не отражает текущую парадигму « Контейнер против презентатора» .

Если вам нужно что-то сделать, например, подтвердить пароль, скорее всего, у вас есть функция, которая это делает. Вы передадите эту функцию своему представлению для повторного использования в качестве опоры.

Контейнеры

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

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

Провайдеры

Если требуется немного больше конфигурации, вы можете использовать модель поставщика / потребителя. Поставщик - это компонент высокого уровня, который оборачивается где-то рядом с верхним объектом приложения (тем, который вы монтируете) и передает часть себя или свойство, настроенное на верхнем уровне, в контекстный API. Затем я устанавливаю элементы контейнера для использования контекста.

Контекстные отношения родитель / потомок не должны быть рядом друг с другом, просто ребенок должен быть каким-то образом спущен. Таким образом, Redux хранит и работает React Router. Я использовал его для предоставления корневого контекста для моих контейнеров отдыха (если я не предоставляю свой собственный).

(примечание: контекстный API помечен как экспериментальный в документах, но я не думаю, что это больше, учитывая, что его использует).

//An example of a Provider component, takes a preconfigured restful.js
//object and makes it available anywhere in the application
export default class RestfulProvider extends React.Component {
	constructor(props){
		super(props);

		if(!("restful" in props)){
			throw Error("Restful service must be provided");
		}
	}

	getChildContext(){
		return {
			api: this.props.restful
		};
	}

	render() {
		return this.props.children;
	}
}

RestfulProvider.childContextTypes = {
	api: React.PropTypes.object
};

Промежуточное

Еще один способ, который я не пробовал, но считал использованным, - это использование промежуточного программного обеспечения в сочетании с Redux. Вы определяете свой сервисный объект вне приложения или, по крайней мере, выше, чем хранилище с избыточностью. Во время создания магазина вы внедряете сервис в промежуточное ПО, а промежуточное ПО обрабатывает любые действия, которые влияют на сервис.

Таким образом, я мог бы внедрить свой объект restful.js в промежуточное ПО и заменить мои методы контейнера независимыми действиями. Мне по-прежнему нужен компонент контейнера для предоставления действий слою представления формы, но connect () и mapDispatchToProps меня там охватили.

Например, новый v4 реагирующий-маршрутизатор-редуктор использует этот метод для воздействия на состояние истории.

//Example middleware from react-router-redux
//History is our service here and actions change it.

import { CALL_HISTORY_METHOD } from './actions'

/**
 * This middleware captures CALL_HISTORY_METHOD actions to redirect to the
 * provided history object. This will prevent these actions from reaching your
 * reducer or any middleware that comes after this one.
 */
export default function routerMiddleware(history) {
  return () => next => action => {
    if (action.type !== CALL_HISTORY_METHOD) {
      return next(action)
    }

    const { payload: { method, args } } = action
    history[method](...args)
  }
}

aphenine
источник
отличный ответ приятель, ты помешал мне делать что-то безмозглое 8) KUDOS !!
csomakk
какой пример использования контейнера?
Сэнсэй
Я не защищаю это, но если вы хотите пойти по пути поиска сервисов (что-то похожее на Angular), вы можете добавить своего рода провайдера «инжектор / контейнер», от которого вы разрешаете сервисы (предварительно зарегистрировав их).
eddiewould
React Hooks приходит на помощь. С помощью Hooks вы можете писать многократно используемую логику без написания класса. responsejs.org/docs/…
Раджа Малик
102

Проблема становится чрезвычайно простой, когда вы понимаете, что служба Angular - это просто объект, который предоставляет набор независимых от контекста методов. Это просто механизм Angular DI, который делает его более сложным. DI полезен, поскольку он заботится о создании и поддержании экземпляров для вас, но он вам на самом деле не нужен.

Рассмотрим популярную библиотеку AJAX с именем axios (о которой вы, вероятно, слышали):

import axios from "axios";
axios.post(...);

Разве это не ведет себя как услуга? Он предоставляет набор методов, отвечающих за определенную логику, и не зависит от основного кода.

В вашем примере речь шла о создании изолированного набора методов для проверки ваших входных данных (например, проверка надежности пароля). Некоторые предлагали поместить эти методы в компоненты, что для меня явно является анти-паттерном. Что если проверка включает в себя выполнение и обработку внутренних вызовов XHR или выполнение сложных вычислений? Вы бы смешали эту логику с обработчиками щелчков мыши и другими специфическими элементами пользовательского интерфейса? Ерунда. То же самое с подходом контейнер / HOC. Обернуть ваш компонент только для добавления метода, который проверит, есть ли в значении цифра? Давай.

Я бы просто создал новый файл с именем скажем «ValidationService.js» и организовал его следующим образом:

const ValidationService = {
    firstValidationMethod: function(value) {
        //inspect the value
    },

    secondValidationMethod: function(value) {
        //inspect the value
    }
};

export default ValidationService;

Тогда в вашем компоненте:

import ValidationService from "./services/ValidationService.js";

...

//inside the component
yourInputChangeHandler(event) {

    if(!ValidationService.firstValidationMethod(event.target.value) {
        //show a validation warning
        return false;
    }
    //proceed
}

Используйте этот сервис из любой точки мира. Если правила проверки изменятся, вам нужно сосредоточиться только на файле ValidationService.js.

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

Войтек Майерски
источник
3
Это способ, которым я бы сделал это тоже. Я весьма удивлен тем, что за этот ответ так мало голосов, так как кажется, что это путь с наименьшим трением. Если ваш сервис зависит от других сервисов, он снова будет импортировать эти сервисы через их модули. Более того, модули по определению являются синглетонами, так что на самом деле больше не нужно работать, чтобы «реализовать его как простой синглтон» - вы получаете такое поведение бесплатно :)
Микки Пури
6
+1 - Хороший ответ, если вы используете только сервисы, предоставляющие функции. Однако сервис Angular - это классы, которые определяются один раз, что обеспечивает больше возможностей, чем просто предоставление функций. Например, вы можете кэшировать объекты как параметр класса обслуживания.
Нино Филиу
6
Это должен быть реальный ответ, а не слишком сложный ответ выше
user1807334
1
Это хороший ответ, за исключением того, что он не "реактивный". DOM не будет обновлять информацию об изменениях в службе.
Defacto
9
А как насчет внедрения зависимости? Службу невозможно смоделировать в вашем компоненте, если вы не добавите ее каким-либо образом. Возможно, обойти это можно с помощью глобального контейнерного объекта верхнего уровня, в котором каждый сервис является полем. Затем в своих тестах вы можете переопределить поля контейнера с макетами для сервисов, которые вы хотите макетировать.
menehune23
34

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

Я поделился логикой, поместив ее в отдельный файл

function format(input) {
    //convert input to output
    return output;
}

module.exports = {
    format: format
};

а затем импортировать его как модуль

import formatter from '../services/formatter.service';

//then in component

    render() {

        return formatter.format(this.props.data);
    }
Kildareflare
источник
8
Это хорошая идея, о которой даже упоминалось в документе React: reactjs.org/docs/composition-vs-inheritance.html Если вы хотите повторно использовать функциональные возможности, не связанные с пользовательским интерфейсом, между компонентами, мы предлагаем извлечь их в отдельный модуль JavaScript. Компоненты могут импортировать его и использовать эту функцию, объект или класс, не расширяя его.
user3426603
На самом деле это единственный ответ, имеющий смысл.
Артем Новиков
33

Имейте в виду, что цель React - лучше соединить вещи, которые логически должны быть связаны. Если вы разрабатываете сложный метод проверки пароля, где он должен быть связан?

Ну, вам придется использовать его каждый раз, когда пользователю нужно ввести новый пароль. Это может быть экран регистрации, экран «забытый пароль», экран «сброс пароля администратора для другого пользователя» и т. Д.

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

Создайте очень маленький компонент React, который состоит исключительно из поля ввода и соответствующей логики проверки. Введите этот компонент во всех формах, для которых может потребоваться ввод пароля.

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

Джейк Роби
источник
11
Какая плохая практика соединять логику и пользовательский интерфейс Для того, чтобы изменить логику, мне нужно прикоснуться к компоненту
Деннис Неруш
14
Реакция принципиально бросает вызов тому предположению, которое вы делаете. Это резко контрастирует с традиционной архитектурой MVC. Это видео довольно хорошо объясняет, почему это так (соответствующий раздел начинается примерно через 2 минуты).
Джейк Роби
8
Что, если ту же логику проверки также необходимо применить к элементу текстовой области? Логика все еще должна быть извлечена в общий файл. Я не думаю, что из коробки есть какая-то эквивалентность из библиотеки реагирования. Angular Service - это инъекции, а среда Angular построена на основе шаблона проектирования внедрения зависимостей, который позволяет экземплярам зависимостей, управляемых Angular. Когда сервис внедряется, в предоставленной области обычно есть синглтон, чтобы иметь такую ​​же услугу в React, сторонняя библиотека DI должна быть введена в приложение.
Downhillski
15
@gravityplanx Мне нравится использовать React. Это не угловой шаблон, это шаблон проектирования программного обеспечения. Мне нравится держать свой ум открытым, заимствуя вещи, которые мне нравятся, из других хороших частей.
Даунхиллски
1
Модули @MickeyPuri ES6 отличаются от Dependency Injection.
Спок
12

Я также пришел из области Angular.js, а сервисы и фабрики в React.js более простые.

Вы можете использовать простые функции или классы, стиль обратного вызова и событие Mobx, как я :)

// Here we have Service class > dont forget that in JS class is Function
class HttpService {
  constructor() {
    this.data = "Hello data from HttpService";
    this.getData = this.getData.bind(this);
  }

  getData() {
    return this.data;
  }
}


// Making Instance of class > it's object now
const http = new HttpService();


// Here is React Class extended By React
class ReactApp extends React.Component {
  state = {
    data: ""
  };

  componentDidMount() {
    const data = http.getData();

    this.setState({
      data: data
    });
  }

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

ReactDOM.render(<ReactApp />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  
  <div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

</body>
</html>

Вот простой пример:

Юрай
источник
React.js - это библиотека пользовательского интерфейса для визуализации и организации компонентов пользовательского интерфейса. Когда речь идет о сервисах, которые могут помочь нам добавить дополнительные функциональные возможности, мы должны создавать коллекции функций, функциональных объектов или классов. Я нашел классы очень полезными, но знаю, что я также играю с функциональным стилем, который также может быть использован для создания помощников для добавления преимущественных функций, выходящих за рамки Reac.js.
Юрай
Просто реализовал это. То, как вы сделали его классом и экспортировали, довольно элегантно.
ГэвинБелсон
10

Та же ситуация: после выполнения нескольких проектов Angular и перехода на React, не имея простого способа предоставления услуг через DI, это кажется отсутствующим элементом (не обращая внимания на особенности сервиса).

Используя контекст и ES7-декораторы, мы можем приблизиться:

https://jaysoo.ca/2015/06/09/react-contexts-and-dependency-injection/

Кажется, эти ребята сделали еще один шаг в другом направлении:

http://blog.wolksoftware.com/dependency-injection-in-react-powered-inversifyjs

Все еще хочется работать против зерна. Я вернусь к этому ответу через 6 месяцев после проведения крупного проекта React.

РЕДАКТИРОВАТЬ: Вернуться через 6 месяцев с еще немного опыта React. Рассмотрим природу логики:

  1. Это связано (только) с пользовательским интерфейсом? Переместить его в компонент (принятый ответ).
  2. Это связано (только) с государственным управлением? Переместите это в гром .
  3. Привязан к обоим? Переместить в отдельный файл, использовать в компоненте через селектор и в thunks.

Некоторые также достигают HOC для повторного использования, но для меня вышеупомянутое охватывает почти все варианты использования. Кроме того, рассмотрите возможность масштабирования управления состояниями с использованием уток, чтобы разделить проблемы и сосредоточить внимание на UI.

венчик
источник
Imho, я думаю, что есть простой способ предоставлять услуги через DI, используя систему модуля ES6
Микки Пури
1
@MickeyPuri, модуль ES6 DI не будет включать иерархическую природу Angular DI, т.е. создание родительских (в DOM) и переопределение сервисов, предоставляемых дочерним компонентам. Модуль DI модуля Imho ES6 сравнивается с бэкэнд-системами DI, такими как Ninject и Structuremap, и находится отдельно от иерархии компонентов DOM, а не на ее основе. Но я хотел бы услышать ваши мысли по этому поводу.
венчик
6

Я тоже из Angular и пробую React, на данный момент, один рекомендуемый (?) Способ, кажется, использует компоненты высокого порядка :

Компонент высшего порядка (HOC) - это продвинутая техника в React для повторного использования логики компонента. HOC сами по себе не являются частью React API. Они - образец, который вытекает из композиционной природы React.

Допустим, у вас есть inputи вам textareaнравится применять одну и ту же логику проверки:

const Input = (props) => (
  <input type="text"
    style={props.style}
    onChange={props.onChange} />
)
const TextArea = (props) => (
  <textarea rows="3"
    style={props.style}
    onChange={props.onChange} >
  </textarea>
)

Затем напишите HOC, который выполняет валидацию и стилизацию обернутого компонента:

function withValidator(WrappedComponent) {
  return class extends React.Component {
    constructor(props) {
      super(props)

      this.validateAndStyle = this.validateAndStyle.bind(this)
      this.state = {
        style: {}
      }
    }

    validateAndStyle(e) {
      const value = e.target.value
      const valid = value && value.length > 3 // shared logic here
      const style = valid ? {} : { border: '2px solid red' }
      console.log(value, valid)
      this.setState({
        style: style
      })
    }

    render() {
      return <WrappedComponent
        onChange={this.validateAndStyle}
        style={this.state.style}
        {...this.props} />
    }
  }
}

Теперь эти HOC имеют одинаковое проверочное поведение:

const InputWithValidator = withValidator(Input)
const TextAreaWithValidator = withValidator(TextArea)

render((
  <div>
    <InputWithValidator />
    <TextAreaWithValidator />
  </div>
), document.getElementById('root'));

Я создал простую демонстрацию .

Редактирование : другая демонстрация использует реквизиты для передачи массива функций, чтобы вы могли делиться логикой, состоящей из нескольких проверяющих функций, через HOCs, например:

<InputWithValidator validators={[validator1,validator2]} />
<TextAreaWithValidator validators={[validator1,validator2]} />

Edit2 : React 16.8+ предоставляет новую функцию, Hook , еще один хороший способ поделиться логикой.

const Input = (props) => {
  const inputValidation = useInputValidation()

  return (
    <input type="text"
    {...inputValidation} />
  )
}

function useInputValidation() {
  const [value, setValue] = useState('')
  const [style, setStyle] = useState({})

  function handleChange(e) {
    const value = e.target.value
    setValue(value)
    const valid = value && value.length > 3 // shared logic here
    const style = valid ? {} : { border: '2px solid red' }
    console.log(value, valid)
    setStyle(style)
  }

  return {
    value,
    style,
    onChange: handleChange
  }
}

https://stackblitz.com/edit/react-shared-validation-logic-using-hook?file=index.js

боб
источник
Спасибо. Я действительно извлек уроки из этого решения. Что делать, если мне нужно иметь более одного валидатора. Например, в дополнение к 3-буквенному валидатору, что, если я хочу иметь другой валидатор, который гарантирует, что никакие цифры не вводятся. Можем ли мы составить валидаторы?
Юсеф Шериф
1
@YoussefSherif Вы можете подготовить несколько проверяющих функций и передать их как опоры HOC, см. Мою правку для другой демонстрации.
Боб
так что HOC в основном является компонентом контейнера?
Сэнсэй
Да, из документа React: «Обратите внимание, что HOC не изменяет входной компонент и не использует наследование для копирования своего поведения. Скорее HOC создает исходный компонент, заключая его в компонент контейнера. HOC - это чистый функция с нулевыми побочными эффектами. "
Боб
1
Требование было ввести логику, я не понимаю, зачем нам нужен HOC для этого. Хотя вы можете сделать это с HOC, это кажется слишком сложным. Мое понимание HOC - это когда есть еще какое-то дополнительное состояние, которое необходимо добавить и управлять, т.е. не чистая логика (как это было здесь).
Микки Пури
4

Сервис не ограничивается Angular, даже в Angular2 + ,

Сервис - это просто набор вспомогательных функций ...

И есть много способов их создания и повторного использования в приложении ...

1) они могут быть отдельными функциями, которые экспортируются из файла js, как показано ниже:

export const firstFunction = () => {
   return "firstFunction";
}

export const secondFunction = () => {
   return "secondFunction";
}
//etc

2) Мы также можем использовать метод фабрики, как, с набором функций ... с ES6 это может быть класс, а не конструктор функции:

class myService {

  constructor() {
    this._data = null;
  }

  setMyService(data) {
    this._data = data;
  }

  getMyService() {
    return this._data;
  }

}

В этом случае вам нужно сделать экземпляр с новым ключом ...

const myServiceInstance = new myService();

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

3) Если ваша функция и утилиты не будут использоваться совместно, вы даже можете поместить их в компонент React, в данном случае, так же, как функцию в вашем компоненте реагирования ...

class Greeting extends React.Component {
  getName() {
    return "Alireza Dezfoolian";
  }

  render() {
    return <h1>Hello, {this.getName()}</h1>;
  }
}

4) Другой способ, которым вы можете обрабатывать вещи, может быть использование Redux , это временное хранилище для вас, поэтому, если у вас есть это в вашем приложении React , оно может помочь вам со многими функциями установки сеттера вами ... Это похоже на большой магазин которые следят за вашими состояниями и могут делиться ими между вашими компонентами, чтобы избавить вас от многих неприятностей, связанных с установщиками-установщиками, которые мы используем в сервисах ...

Всегда полезно делать DRY-код, а не повторять то, что необходимо использовать, чтобы сделать код многократно используемым и читабельным, но не пытайтесь следовать угловым путям в приложении React , как упоминалось в пункте 4, использование Redux может снизить ваши потребности в сервисы, и вы ограничиваете их использование для некоторых повторно используемых вспомогательных функций, таких как пункт 1 ...

Алиреза
источник
Конечно, вы можете найти его на моем личном сайте, который является ссылкой со страницы моего профиля ...
Alireza
«Не следуйте Angular-путям в React». Гм. Angular продвигает использование Redux и передает хранилище в презентационные компоненты, используя Observables и Redux-подобное управление состояниями, например RxJS / Store. .. ты имел ввиду AngularJS? Потому что это другое дело
Спок
1

Я в такой же загрузке, как ты. В случае, если вы упомянули, я бы реализовал компонент пользовательского интерфейса проверки ввода как компонент React.

Я согласен, что реализация самой логики валидации не должна (должна) быть связана. Поэтому я бы поместил его в отдельный модуль JS.

То есть для логики, которая не должна быть связана, используйте модуль / класс JS в отдельном файле и используйте require / import, чтобы отсоединить компонент от «службы».

Это позволяет вводить зависимости и тестировать модуль независимо друг от друга.

sibidiba
источник
1

или вы можете ввести наследование класса "http" в React Component

через объект опоры.

  1. Обновить :

    ReactDOM.render(<ReactApp data={app} />, document.getElementById('root'));
  2. Просто отредактируйте React Component ReactApp следующим образом:

    class ReactApp extends React.Component {
    
    state = {
    
        data: ''
    
    }
    
        render(){
    
        return (
            <div>
            {this.props.data.getData()}      
            </div>
    
        )
        }
    }
Юрай
источник
0

Ну, наиболее часто используемый шаблон для логики многократного использования, с которым я сталкивался, это либо написание ловушки, либо создание файла утилит. Это зависит от того, чего вы хотите достичь.

hooks/useForm.js

Например, если вы хотите проверить данные формы, я бы создал пользовательский хук с именем useForm.js и предоставил бы ему данные формы, а взамен он вернул бы мне объект, содержащий две вещи:

Object: {
    value,
    error,
}

Вы можете определенно вернуть больше вещей из этого по мере вашего прогресса.

utils/URL.js

Другой пример: вы хотите извлечь некоторую информацию из URL-адреса, а затем создать для нее файл utils, содержащий функцию, и импортировать ее при необходимости:

 export function getURLParam(p) {
...
}
Мухаммед Шахриар
источник