Надання послуг у програмі React


176

Я приїжджаю з кутового світу, де можу отримати логіку на службі / фабриці та споживати їх у своїх контролерах.

Я намагаюся зрозуміти, як я можу досягти цього в додатку React.

Скажімо, у мене є компонент, який перевіряє введення пароля користувача (це сила). Це логіка досить складна, тому я не хочу писати її в складі сам.

Де мені написати цю логіку? У магазині, якщо я використовую флюс? Або є кращий варіант?


Ви можете скористатися пакетом і подивитися, як вони це роблять - npmjs.com/package/react-password-strength-meter
James111

11
Міцність пароля - лише приклад. Я шукаю більш загальну найкращу практику
Денніс Неруш

Можливо, вам доведеться це зробити на сервері?
Джеймс111

2
Ні. Тільки логіка на стороні клієнта, яка не повинна знаходитися безпосередньо в компоненті. Перевірка надійності пароля - лише приклад
Денніс Неруш

4
Якщо у вас багато таких функцій, ви можете зберігати їх у файлі помічника і просто вимагати його у своєму компонентному файлі для використання. Якщо це одна функція, яка стосується виключно цього компонента, вона, ймовірно, повинна жити там, незалежно від складності.
Джессі Кернаган

Відповіді:


60

Перша відповідь не відображає поточну парадигму Container vs Presenter .

Якщо вам потрібно щось зробити, наприклад перевірити пароль, ви, ймовірно, матимете функцію, яка це робить. Ви передасте цю функцію в режим повторного використання як опора.

Контейнери

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

Перевірка може бути виконана у властивостях контейнера, але якщо ви використовуєте сторонній валідатор або будь-яку просту службу перевірки, ви можете використовувати службу як властивість компонента контейнера та використовувати його у методах контейнера. Я зробив це для спокійних компонентів, і це працює дуже добре.

Постачальники

Якщо потрібно трохи більше конфігурації, ви можете використовувати модель постачальника / споживача. Постачальник - це компонент високого рівня, який загортається десь поблизу та під його об'єктом верхнього додатка (той, який ви встановлюєте), і постачає частину себе або властивість, налаштовану у верхньому шарі, API контексту. Потім я встановлюю свої елементи контейнера, щоб споживати контекст.

Контекстні відносини батько / дитина не повинні бути поруч один з одним, просто дитина має бути якимсь чином спускатися. Таким чином функціонують магазини Redux та маршрутизатор React. Я використовував його для забезпечення кореневого спокійного контексту для своїх контейнерів для відпочинку (якщо я не надаю свій власний).

(зауважте: контекстний 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 в середнє програмне забезпечення і замінити свої методи контейнерів незалежними діями. Мені все одно знадобиться компонент контейнера для надання дій шару перегляду форми, але підключити () і mapDispatchToProps мене там охоплює.

Новий v4-реактор-маршрутизатор 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)
  }
}


чудова відповідь, товариш, ти мені заважав робити речі без мозків 8) KUDOS !!
csomakk

Яке використання для прикладу контейнера?
сенсей

Я не виступаю за це, але якщо ви хочете спуститися по контуру локатора обслуговування (щось подібне до Angular), ви можете додати якогось постачальника послуг "інжектор / контейнер", з якого ви вирішуєте послуги (попередньо зареєструвавши їх).
eddiewould

На допомогу приходить реактивний гачок. За допомогою гачків ви можете писати логіку багаторазового використання без написання класу. reactjs.org/docs/…
Раджа Малік

102

Питання стає надзвичайно простим, коли ви розумієте, що послуга 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 не оновлюватиме змінні зміни в сервісі.
Дефакто

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

8
Це гарна ідея, як навіть згадується в документі React: reactjs.org/docs/composition-vs-inheritance.html Якщо ви хочете повторно використовувати функціонал, що не користується інтерфейсом, між компонентами, пропонуємо витягнути його в окремий модуль JavaScript. Компоненти можуть імпортувати його та використовувати цю функцію, об’єкт чи клас, не розширюючи його.
користувач3426603

Це насправді єдина відповідь, яка має сенс.
Артем Новіков

33

Майте на увазі, що мета React - краще поєднати речі, які логічно повинні бути пов'язані. Якщо ви розробляєте складний метод "перевірити пароль", де його слід поєднати?

Ну, вам потрібно буде використовувати його щоразу, коли користувачеві потрібно ввести новий пароль. Це може бути на екрані реєстрації, екрані "забутого пароля", екрані адміністратора "скидання пароля для іншого користувача" тощо.

Але в будь-якому з цих випадків воно завжди буде прив’язане до якогось поля введення тексту. Тож саме тут слід поєднатись.

Створіть дуже маленький компонент React, який складається виключно з поля введення та відповідної логіки перевірки. Введіть цей компонент у всі форми, які можуть мати введення пароля.

Це по суті такий же результат, як і служба / фабрика для логіки, але ви зв'язуєте її безпосередньо з входом. Тому вам ніколи не потрібно вказувати цю функцію, де шукати вхід для перевірки, оскільки вона постійно пов'язана між собою.


11
Яка погана практика поєднувати логіку та інтерфейс користувача. Для зміни логіки мені доведеться торкнутися компонента
Денніс Неруш

14
Реагуйте принципово на виклики того припущення, яке ви робите. Він суворо контрастує з традиційною архітектурою MVC. Це відео дуже добре пояснює, чому це відбувається (відповідний розділ починається приблизно через 2 хвилини).
Джейк Робі

8
Що робити, якщо таку ж логіку перевірки також потрібно застосувати до елемента області тексту? Логіку все-таки потрібно витягти в спільний файл. Я не думаю, що з коробки є еквівалент бібліотеки реакцій. Кутовий сервіс є ін'єкційними, і кутовий каркас побудований на основі шаблону проектування інжекційних залежностей, що дозволяє застосовувати випадки залежностей, якими керує Angular. Коли послуга вводиться, як правило, в наданому обсязі є одинарний ключ, щоб мати ту саму службу в React, до програми потрібно ввести сторонній DI lib.
Downhillski

15
@gravityplanx Мені подобається використовувати React. Це не кутовий малюнок, це шаблон дизайну програмного забезпечення. Мені подобається постійно відкриватись, запозичуючи речі, які мені подобаються, з інших хороших частин.
Downhillski

1
Модулі @MickeyPuri ES6 не те саме, що введення залежностей.
Спок

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

Така ж ситуація: зробивши декілька кутових проектів та переїхавши до 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.

EDIT: Назад через 6 місяців із ще деяким досвідом React. Розглянемо природу логіки:

  1. Чи прив’язаний він (лише) до інтерфейсу користувача? Перемістіть його в компонент (прийнята відповідь).
  2. Чи прив’язаний він (лише) до державного управління? Перемістіть його в грудку .
  3. Зв'язали обох? Перемістіть до окремого файлу, споживайте в компоненті через селектор і в грудях.

Деякі також звертаються до HOC для повторного використання, але для мене вищезазначене охоплює майже всі випадки використання. Крім того, розгляньте можливість масштабування управління державою за допомогою качок, щоб утримати проблеми окремі та концентровані на державному інтерфейсі.


Имхо Я думаю , що це простий спосіб для надання послуг через DI, за допомогою системи ES6 Module
Міккі Пурі

1
@MickeyPuri, модуль DI6 ES6 не включатиме ієрархічну сутність кутового DI, тобто. батьки (у DOM) надання даних про моніторинг та перевагу послуг, що надаються до дочірніх компонентів. Модуль DI Imho ES6 порівняння наближається до таких резервних систем DI, як Ninject і Structuremap, які сидять окремо від ієрархії компонентів DOM, а не засновані на них. Але я хотів би почути ваші думки з цього приводу.
віночок

6

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

Компонент вищого порядку (HOC) - це вдосконалена методика React для повторного використання логіки компонентів. HOC самі по собі не є частиною API React. Вони є візерунком, який випливає з композиційної природи 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} />
    }
  }
}

Тепер ці ГОС поділяють таку саму перевіряючу поведінку:

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


Дякую. Я справді навчився цьому рішенню. Що робити, якщо мені потрібно мати більше одного валідатора. Наприклад, на додаток до валідатора з трьома літерами, що робити, якщо я хочу мати ще один валідатор, який гарантує відсутність цифр. Чи можемо ми скласти валідатори?
Шериф Юсефа

1
@YoussefSherif Ви можете підготувати кілька функцій перевірки та передавати їх у якості реквізиту HOC, перегляньте мою редакцію для іншого демонстраційного повідомлення.
боб

так що HOC в основному є контейнерним компонентом?
сенсей

Так, від документа React: "Зауважте, що HOC не змінює вхідний компонент, а також не використовує спадкування для копіювання своєї поведінки. Скоріше, HOC створює оригінальний компонент, загортаючи його в компонент контейнера. HOC - це чистий функція з нульовими побічними ефектами. "
боб

1
Вимога полягала в тому, щоб вводити логіку, я не бачу, для чого нам потрібен ВОК. Хоча ви можете це зробити з HOC, це відчувається надто складним. Моє розуміння HOCs - це коли є також якийсь додатковий стан, який потрібно додати та керувати, тобто не є чистою логікою (як це було тут).
Міккі Пурі

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


Звичайно, ви можете знайти його на моєму особистому веб-сайті, який посилається на моїй сторінці профілю ...
Аліреза

"Не слідкуйте за кутовими способами в React" .. ahem Angular просуває за допомогою Redux і передає магазин до презентаційних компонентів за допомогою спостережуваних та Redux-подібних керувань державою, таких як RxJS / Store. .. ти мав на увазі AngularJS? Тому що це інша річ
Спок

1

Я в такому ж чоботі, як і ти. У випадку, про який ви згадуєте, я би реалізував компонент інтерфейсу перевірки входу як компонент React.

Я погоджуюся, що реалізація логіки перевірки не повинна (повинна) бути поєднаною. Тому я б розмістив його в окремому модулі JS.

Тобто, для логіки, яку не слід поєднувати, використовуйте модуль / клас JS в окремому файлі, а використовуйте вимагати / імпортувати для від'єднання компонента від "служби".

Це дозволяє незалежно вводити залежність та одиничне тестування.


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-адреси, тоді я створив би файл утиліти для неї, який містить функцію та імпортував її там, де потрібно:

 export function getURLParam(p) {
...
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.