我来自angular世界,在那里我可以提取逻辑到服务/工厂,并在我的控制器中使用它们。

我试图了解如何在React应用程序中实现相同的功能。

假设我有一个验证用户密码输入的组件(它的强度)。它的逻辑相当复杂,因此我不想把它写在组件中。

我应该把这个逻辑写在哪里?如果我在商店里使用助焊剂?还是有更好的选择?


当前回答

当你意识到Angular服务只是一个对象,它交付了一组与上下文无关的方法时,问题就变得极其简单了。只是Angular的DI机制让它看起来更复杂。DI非常有用,因为它负责为您创建和维护实例,但您并不真正需要它。

考虑一个名为axios的流行AJAX库(你可能听说过):

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

它不是一个服务吗?它提供了一组负责某些特定逻辑的方法,并且独立于主代码。

Your example case was about creating an isolated set of methods for validating your inputs (e.g. checking the password strength). Some suggested to put these methods inside the components which for me is clearly an anti-pattern. What if the validation involves making and processing XHR backend calls or doing complex calculations? Would you mix this logic with mouse click handlers and other UI specific stuff? Nonsense. The same with the container/HOC approach. Wrapping your component just for adding a method which will check whether the value has a digit in it? Come on.

我只需要创建一个名为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文件。

您可能需要一个依赖于其他服务的更复杂的服务。在这种情况下,您的服务文件可能返回一个类构造函数而不是一个静态对象,因此您可以在组件中自己创建该对象的实例。您还可以考虑实现一个简单的单例,以确保在整个应用程序中始终只有一个服务对象实例在使用。

其他回答

Service并不局限于Angular,甚至在Angular2+中也是如此,

Service只是helper函数的集合…

有很多方法可以创建它们并在应用程序中重用它们……

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

}

在这种情况下,你需要用new key创建一个实例…

const myServiceInstance = new myService();

同样,在这种情况下,每个实例都有自己的生命周期,所以如果你想跨界共享它要小心,在这种情况下,你应该只导出你想要的实例……

3)如果你的函数和utils不会被共享,你甚至可以把它们放在React组件中,在这种情况下,就像函数在React组件中一样…

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

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

4)另一种你可以处理事情的方法,可以使用Redux,它是你的临时存储,所以如果你在你的React应用程序中有它,它可以帮助你使用许多getter setter函数…它就像一个大的存储,可以跟踪你的状态,并可以在你的组件之间共享它,所以可以摆脱我们在服务中使用的getter setter的许多痛苦……

做一个DRY代码总是好的,不要重复需要使用的东西来让代码可重用和可读,但不要试图在React应用中遵循Angular的方法,就像第4项提到的,使用Redux可以减少你对服务的需求,你可以限制在一些可重用的帮助函数上使用它们,比如第1项……

同样的情况:在做了多个Angular项目后转向React,没有一个简单的方法通过DI提供服务似乎是一个缺失的部分(抛开服务的细节不谈)。

使用context和ES7装饰器,我们可以接近:

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

似乎这些家伙在不同的方向上更进一步:

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

还是觉得有违常理。在承担一个主要的React项目后,将在6个月的时间内重新审视这个答案。

编辑:6个月后回来,有了更多的React经验。考虑一下逻辑的本质:

它是否(仅)绑定到UI?将其移动到组件中(已接受的答案)。 它(只)与国家管理有关吗?移动到一个坦克。 两者都有关系?移动到单独的文件,在组件中通过选择器和坦克消费。

有些人还使用hoc进行重用,但对我来说,以上几乎涵盖了所有用例。另外,考虑使用鸭子来扩展状态管理,以保持关注点的独立性和以状态ui为中心。

当你意识到Angular服务只是一个对象,它交付了一组与上下文无关的方法时,问题就变得极其简单了。只是Angular的DI机制让它看起来更复杂。DI非常有用,因为它负责为您创建和维护实例,但您并不真正需要它。

考虑一个名为axios的流行AJAX库(你可能听说过):

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

它不是一个服务吗?它提供了一组负责某些特定逻辑的方法,并且独立于主代码。

Your example case was about creating an isolated set of methods for validating your inputs (e.g. checking the password strength). Some suggested to put these methods inside the components which for me is clearly an anti-pattern. What if the validation involves making and processing XHR backend calls or doing complex calculations? Would you mix this logic with mouse click handlers and other UI specific stuff? Nonsense. The same with the container/HOC approach. Wrapping your component just for adding a method which will check whether the value has a digit in it? Come on.

我只需要创建一个名为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文件。

您可能需要一个依赖于其他服务的更复杂的服务。在这种情况下,您的服务文件可能返回一个类构造函数而不是一个静态对象,因此您可以在组件中自己创建该对象的实例。您还可以考虑实现一个简单的单例,以确保在整个应用程序中始终只有一个服务对象实例在使用。

我也来自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'));

我创建了一个简单的演示。

编辑:另一个演示是使用props来传递一个函数数组,这样你就可以在hoc之间共享由多个验证函数组成的逻辑,比如:

<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

请记住,React的目的是更好地耦合逻辑上应该耦合的东西。如果您正在设计一个复杂的“验证密码”方法,它应该耦合在哪里?

每次用户需要输入新密码时,你都需要用到它。这可能出现在注册屏幕、“忘记密码”屏幕、管理员“为其他用户重置密码”屏幕等。

但在这些情况下,它总是会绑定到某个文本输入域。这就是它们应该耦合的地方。

制作一个非常小的React组件,只包含一个输入字段和相关的验证逻辑。在所有可能需要密码输入的表单中输入该组件。

这本质上与为逻辑提供服务/工厂的结果相同,但您将其直接耦合到输入。因此,现在您永远不需要告诉该函数在哪里查找它的验证输入,因为它是永久地绑定在一起的。