我来自角度世界,在那里我可以将逻辑提取到服务/工厂并在我的控制器中使用它们。
我试图了解如何在React应用程序中实现相同目的。
假设我有一个验证用户密码输入的组件(它的强度)。它的逻辑非常复杂,因此我不想在它自己的组件中编写它。
我应该在哪里写这个逻辑?在商店里,如果我使用助焊剂?或者有更好的选择吗?
答案 0 :(得分:42)
当您意识到Angular服务只是一个提供一组上下文无关方法的对象时,问题变得非常简单。只是Angular DI机制使它看起来更加复杂。 DI非常有用,因为它可以为您创建和维护实例,但您实际上并不需要它。
考虑一个流行的AJAX库axios(您可能听说过):
import axios from "axios";
axios.post(...);
它不是一种服务吗?它提供了一组负责某些特定逻辑的方法,并且独立于主代码。
您的示例案例是关于创建一组隔离的方法来验证您的输入(例如,检查密码强度)。有些人建议将这些方法放在组件中,这对我来说显然是一种反模式。如果验证涉及进行和处理XHR后端调用或进行复杂的计算,该怎么办?您会将这种逻辑与鼠标单击处理程序和其他特定于UI的东西混合使用吗?废话。与容器/ 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文件。
您可能需要更复杂的服务,具体取决于其他服务。在这种情况下,您的服务文件可能会返回类构造函数而不是静态对象,因此您可以自己在组件中创建该对象的实例。您还可以考虑实现一个简单的单例,以确保整个应用程序中始终只使用一个服务对象实例。
答案 1 :(得分:41)
第一个答案并不反映当前的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
};
我还没有尝试过,但看过用过的另一种方法是将中间件与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)
}
}
答案 2 :(得分:29)
请记住,React的目的是更好地结合逻辑上应该耦合的东西。如果你正在设计一个复杂的“验证密码”方法,它应该耦合在哪里?
每次用户需要输入新密码时,您都需要使用它。这可以在注册屏幕上,“忘记密码”屏幕,管理员“重置另一个用户的密码”屏幕等。
但在任何一种情况下,它总是与某些文本输入字段绑定。这就是它应该耦合的地方。
创建一个非常小的React组件,它只包含一个输入字段和相关的验证逻辑。在所有可能想要输入密码的表单中输入该组件。
与逻辑服务/工厂的结果基本相同,但是您将它直接耦合到输入。因此,您现在永远不需要告诉该函数在哪里查找它的验证输入,因为它永久地绑在一起。
答案 3 :(得分:22)
我需要在多个组件之间共享一些格式化逻辑,而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);
}
答案 4 :(得分:9)
同样的情况:完成了多个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经验。考虑逻辑的本质:
答案 5 :(得分: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"));
<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>
这是一个简单的例子:
答案 6 :(得分:2)
我也来自Angular并试用React,截至目前,一种推荐(?)方式似乎正在使用High-Order Components:
高阶分量(HOC)是React for中的一种高级技术 重用组件逻辑。 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'));
我创建了一个简单的demo。
编辑:另一个demo正在使用道具传递一系列函数,以便您可以在HOC
之间共享由多个验证函数组成的逻辑,如:
<InputWithValidator validators={[validator1,validator2]} />
<TextAreaWithValidator validators={[validator1,validator2]} />
答案 7 :(得分:2)
即使在 Angular2 + ,
中,服务也不仅限于Angular服务只是辅助功能的集合......
有很多方法可以创建它们并在整个应用程序中重用它们......
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)如果你的函数和utils不会被共享,你甚至可以将它们放在React组件中,在这种情况下,就像你的react组件中的函数一样......
class Greeting extends React.Component {
getName() {
return "Alireza Dezfoolian";
}
render() {
return <h1>Hello, {this.getName()}</h1>;
}
}
4)您处理事情的另一种方式可能是使用 Redux ,它是您的临时商店,所以如果您拥有 >反应应用程序,它可以帮助您使用许多 getter setter功能 ...它就像一个大商店,可以跟踪您的状态并可以在您的状态中共享组件,所以可以摆脱我们在服务中使用的getter setter东西的许多痛苦......
执行 DRY代码总是好的,而不是重复需要用来使代码可重用和可读的内容,但不要试图关注如第4项所述,使用Redux的React app 中的Angular方式可以减少您对服务的需求,并限制将它们用于某些可重复使用的辅助函数,如第1项......
答案 8 :(得分:2)
如果您还在寻找像 Angular 这样的服务,您可以试试 react-rxbuilder 库
可以使用@Injectable
注册服务,然后可以使用useService
或者CountService.ins
在组件中使用服务
import { RxService, Injectable, useService } from "react-rxbuilder";
@Injectable()
export class CountService {
static ins: CountService;
count = 0;
inc() {
this.count++;
}
}
export default function App() {
const [s] = useService(CountService);
return (
<div className="App">
<h1>{s.count}</h1>
<button onClick={s.inc}>inc</button>
</div>
);
}
// Finally use `RxService` in your root component
render(<RxService>{() => <App />}</RxService>, document.getElementById("root"));
注意事项
答案 9 :(得分:1)
我和你一样在同一个靴子里。在你提到的情况下,我会将输入验证UI组件实现为React组件。
我同意验证逻辑本身的实现应该(必须)不耦合。因此我会把它放到一个单独的JS模块中。
也就是说,对于不应该耦合的逻辑,在单独的文件中使用JS模块/类,并使用require / import将组件与“服务”分离。
这允许依赖注入和单独测试两者。
答案 10 :(得分:1)
或者您可以将类继承“http”注入React Component
通过道具对象。
更新:
ReactDOM.render(<ReactApp data={app} />, document.getElementById('root'));
只需编辑React Component ReactApp,如下所示:
class ReactApp extends React.Component {
state = {
data: ''
}
render(){
return (
<div>
{this.props.data.getData()}
</div>
)
}
}
答案 11 :(得分:0)
我遇到的最常用的可重用逻辑模式是编写钩子或创建utils文件。这取决于您要完成什么。
hooks/useForm.js
就像您要验证表单数据一样,我将创建一个名为 useForm.js 的自定义钩子,并提供表单数据,作为回报,它将返回一个包含两件事的对象:
Object: {
value,
error,
}
随着您的前进,您绝对可以从中返回更多的东西。
utils/URL.js
另一个例子是,您想从URL中提取一些信息,然后为它创建一个包含函数的utils文件,并在需要时将其导入:
export function getURLParam(p) {
...
}
答案 12 :(得分:-2)
使用DRY方法将帮助您在ReactJS中编写更好的代码。 ReactJS的真正作用是使UI更具交互性。每次有更新时,它都只需更改更改即可,而无需再次执行整个过程,从而实现了轻松过渡。通常,这有助于简化调试过程和更可预测的代码。 7 Advantages of ReactJS for Building Interactive User Interfaces上的该博客将帮助您更清楚地理解事物。