在React应用程序中提供服务

Den*_*ush 126 reactjs reactjs-flux

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

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

假设我有一个验证用户密码输入的组件(它的强度).它的逻辑非常复杂,因此我不想在它自己的组件中编写它.

我应该在哪里写这个逻辑?在商店里,如果我使用助焊剂?或者有更好的选择吗?

Woj*_*ski 70

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

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

import axios from "axios";
axios.post(...);
Run Code Online (Sandbox Code Playgroud)

它不是一种服务吗?它提供了一组负责某些特定逻辑的方法,并且与主代码无关.

您的示例案例是关于创建一组用于验证输入的隔离方法(例如,检查密码强度).有人建议将这些方法放在组件中,这对我来说显然是一种反模式.如果验证涉及制作和处理XHR后端调用或进行复杂计算,该怎么办?您会将此逻辑与鼠标单击处理程序和其他UI特定的东西混合使用吗?废话.与容器/ HOC方法相同.包装组件只是为了添加一个方法来检查值中是否有数字?来吧.

我只想创建一个名为'ValidationService.js'的新文件,并按如下方式组织它:

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

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

export default ValidationService;
Run Code Online (Sandbox Code Playgroud)

然后在你的组件中:

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

...

//inside the component
yourInputChangeHandler(event) {

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

在任何您想要的地方使用此服务.如果验证规则发生更改,则只需关注ValidationService.js文件.

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

  • 那么依赖注入呢?该服务不可能在您的组件中进行模拟,除非您以某种方式注入它。也许拥有一个将每个服务作为一个字段的顶级“容器”全局对象可以解决这个问题。然后在测试中,您可以使用要模拟的服务的模拟覆盖容器字段。 (17认同)
  • +1-如果您仅使用提供功能的服务,那就是不错的答案。**但是**,Angular的服务是一次定义的类,因此提供的功能比仅仅提供功能要多。例如,您可以将对象缓存为服务类参数。 (4认同)
  • 这应该是真正的答案,而不是上面的过于复杂的回答 (4认同)
  • 我也是这样做的。我很惊讶这个答案投票很少,因为这似乎是摩擦最小的方法。如果您的服务依赖于其他服务,那么它将再次通过其模块导入那些其他服务。此外,按照定义,模块是单例,因此实际上不需要进一步的工作即可“将其实现为简单的单例”-您可以免费获得该行为:) (2认同)
  • @Defacto 该问题的一个解决方案是反应式扩展(可观察量)。订阅从服务返回的可观察流并使用主题将更改“推送”到组件。就我个人而言,我更喜欢这个答案,因为它允许我将业务逻辑移到组件之外,使我的组件尽可能小,而不需要手动处理数据。不太复杂的部分 => 更少的错误/更容易维护。 (2认同)
  • 我只是_部分_同意这一点。这仅适用于_无状态_函数逻辑。您将如何实现在内存中_存储变量_的任务,为此通常使用角度服务。 (2认同)
  • @sasebot 是的,这是针对功能代码的。如果您想存储变量(当然在组件状态之外),您当然可以自由地使用实现观察者模式。但我更喜欢使用操作和减速器,并将所有状态保留在 Redux 中。我的 React 项目中的所有逻辑都是 100% 纯函数。 (2认同)
  • 正如许多人评论的那样,这根本不是 Angular 服务!Angular 服务有范围、状态和生命周期!您的建议只有一个作用域,没有状态,显然生命周期也不适用于纯函数。 (2认同)

aph*_*ine 46

第一个答案并不反映当前的Container vs Presenter范例.

如果你需要做一些事情,比如验证密码,你可能会有一个功能来完成它.您将该功能作为道具传递给您的可重用视图.

集装箱

因此,正确的方法是编写一个ValidatorContainer,它将该函数作为属性,并将表单包装在其中,将正确的props传递给子.在您的视图中,验证器容器包装您的视图,视图使用容器逻辑.

验证可以在容器的属性中完成,但是您使用的是第三方验证器或任何简单的验证服务,您可以将该服务用作容器组件的属性并在容器的方法中使用它.我已经为宁静的组件做了这个,它运行得很好.

供应商

如果需要更多配置,您可以使用提供者/消费者模型.提供程序是一个高级组件,它包装在顶部应用程序对象(您安装的对象)附近和下方的某个位置,并将其自身的一部分或顶层中配置的属性提供给上下文API.然后我设置我的容器元素以使用上下文.

父/子上下文关系不必彼此靠近,只要孩子必须以某种方式下降.Redux以这种方式存储和React Router功能.我用它来为我的休息容器提供一个root restful上下文(如果我不提供我自己的容器).

(注意:上下文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
};
Run Code Online (Sandbox Code Playgroud)

中间件

我还没有尝试过,但看过用过的另一种方法是将中间件与Redux结合使用.您可以在应用程序之外定义服务对象,或者至少高于redux存储.在存储创建期间,您将服务注入中间件,中间件处理影响服务的任何操作.

通过这种方式,我可以将restful.js对象注入中间件并用独立的操作替换我的容器方法.我仍然需要一个容器组件来为表单视图层提供操作,但是connect()和mapDispatchToProps让我在那里.

例如,新的v4 react-router-redux使用此方法来影响历史状态.

//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)
  }
}
Run Code Online (Sandbox Code Playgroud)


Jak*_*oby 29

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

那么每次用户需要输入新密码时你都需要使用它.这可以在注册屏幕上,"忘记密码"屏幕,管理员"重置另一个用户的密码"屏幕等.

但在任何一种情况下,它总是与某些文本输入字段相关联.这就是它应该耦合的地方.

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

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

  • @gravityplanx我喜欢使用React.这不是角度模式,这是软件设计模式.我喜欢在从其他好的部分借用我喜欢的东西时保持开放的心态. (14认同)
  • React从根本上挑战了你所做的假设.它与传统的MVC架构形成鲜明对比.[本视频](https://youtu.be/x7cQ3mrcKaY)可以很好地解释原因(相关部分在2分钟左右开始). (12认同)
  • 将逻辑和UI结合起来是不好的做法.为了改变逻辑,我将不得不触摸组件 (9认同)
  • 如果还需要将相同的验证逻辑应用于文本区域元素,该怎么办?逻辑仍然需要提取到共享文件中.我不认为开箱即用反应库有任何等价.Angular Service是可注入的,Angular框架构建在依赖注入设计模式之上,允许Angular管理依赖项的实例.注入服务时,在提供的范围内通常有一个单例,要在React中提供相同的服务,需要将第三方DI lib引入应用程序. (6认同)
  • @MickeyPuri ES6 模块与依赖注入不同。 (2认同)
  • @MickeyPuri 模块系统是 es6 引擎如何将不同的文件连接到引擎知道从哪里读取代码的方式。DI 是运行时行为,它与编译器/引擎无关。 (2认同)

Kil*_*are 24

我需要在多个组件之间共享一些格式化逻辑,而Angular开发人员也自然倾向于服务.

我把它放在一个单独的文件中来共享逻辑

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

module.exports = {
    format: format
};
Run Code Online (Sandbox Code Playgroud)

然后将其作为模块导入

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

//then in component

    render() {

        return formatter.format(this.props.data);
    }
Run Code Online (Sandbox Code Playgroud)

  • 这是一个好主意,甚至在React文档中提到:https://reactjs.org/docs/composition-vs-inheritance.html如果要在组件之间重用非UI功能,我们建议将其提取到单独的JavaScript模块中.组件可以导入它并使用该函数,对象或类,而无需扩展它. (7认同)
  • 这实际上是这里唯一有意义的答案。 (6认同)
  • 这个答案中的依赖注入在哪里? (2认同)

cor*_*lla 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

仍感觉像是在反对谷物.在进行一个重大的React项目后,将在6个月后重新审视这个答案.

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

  1. 是(仅)与UI绑定?将其移动到组件中(已接受的答案).
  2. 它(仅)与国家管理有关吗?把它移到thunk中.
  3. 绑到两个?移动到单独的文件,通过选择器和thunk 消耗组件.

有些人还可以将HOC用于重复使用,但对我来说,上述内容几乎涵盖了所有用例.此外,考虑使用ducks来扩展状态管理,以使问题保持​​独立并以状态UI为中心.

  • @MickeyPuri,ES6 模块 DI 不会包含 Angular DI 的分层性质,即。父组件(在 DOM 中)实例化并覆盖提供给子组件的服务。恕我直言,ES6 模块 DI 与 Ninject 和 Structuremap 等后端 DI 系统更接近,与 DOM 组件层次结构分开,而不是基于 DOM 组件层次结构。但我想听听你对此的想法。 (2认同)

Jur*_*raj 9

我也来自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"));
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

这是一个简单的例子:


bob*_*bob 8

我也来自 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>
)
Run Code Online (Sandbox Code Playgroud)

然后编写一个 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} />
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

现在这些 HOC 共享相同的验证行为:

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

render((
  <div>
    <InputWithValidator />
    <TextAreaWithValidator />
  </div>
), document.getElementById('root'));
Run Code Online (Sandbox Code Playgroud)

我创建了一个简单的演示

编辑:另一个演示是使用 props 传递函数数组,以便您可以跨HOCs共享由多个验证函数组成的逻辑,例如:

<InputWithValidator validators={[validator1,validator2]} />
<TextAreaWithValidator validators={[validator1,validator2]} />
Run Code Online (Sandbox Code Playgroud)

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
  }
}
Run Code Online (Sandbox Code Playgroud)

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