我来自Angular世界,在那里我可以将逻辑提取到服务/工厂中,并在控制器中使用它们。
我试图了解如何在React应用程序中实现相同的功能。
假设我有一个组件,用于验证用户的密码输入(其强度)。它的逻辑非常复杂,因此我不想在组件本身中编写它。
我应该在哪里编写这个逻辑?在使用flux时,在一个存储区吗?还是有更好的选择?
我来自Angular世界,在那里我可以将逻辑提取到服务/工厂中,并在控制器中使用它们。
我试图了解如何在React应用程序中实现相同的功能。
假设我有一个组件,用于验证用户的密码输入(其强度)。它的逻辑非常复杂,因此我不想在组件本身中编写它。
我应该在哪里编写这个逻辑?在使用flux时,在一个存储区吗?还是有更好的选择?
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 文件即可。
您可能需要一个依赖于其他服务的更复杂的服务。在这种情况下,您的服务文件可能会返回一个类构造函数,而不是静态对象,因此您可以在组件中自己创建对象实例。您还可以考虑实现一个简单的单例,以确保整个应用程序中始终只有一个服务对象实例在使用。
//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)
}
}
我需要一些格式化逻辑在多个组件之间共享,并且作为 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);
}
请记住,React 的目的是更好地耦合那些在逻辑上应该被耦合在一起的东西。如果您正在设计一个复杂的“验证密码”方法,那么它应该与哪里相耦合?
好吧,每当用户需要输入新密码时,您都需要使用它。这可能出现在注册屏幕、“忘记密码”屏幕、管理员“为其他用户重置密码”屏幕等等。
但在任何这些情况下,它始终会与某个文本输入字段相关联。所以它应该与此相关联。
创建一个非常小的 React 组件,仅包含一个输入字段和相关的验证逻辑。在所有可能需要密码输入的表单中输入该组件。
这实际上与使用服务/工厂获得相同的结果,但是您将其直接与输入字段耦合在一起。因此,您现在永远不需要告诉该函数在哪里查找其验证输入,因为它们被永久地绑定在一起。
相同情况:已经完成了多个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经验。考虑逻辑的性质:
一些人也会使用HOCs进行重复使用,但对我来说,上述几点几乎涵盖了所有用例。此外,考虑使用ducks扩展状态管理,以保持关注点分离和状态UI为中心。
我也来自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>
这里是一个简单的例子:
我也来自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>
)
然后编写一个高阶组件,对包装的组件进行验证和样式设置:
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'));
我创建了一个简单的演示。
编辑:另一个演示使用props传递函数数组,以便您可以跨HOC
共享由多个验证函数组成的逻辑:
<InputWithValidator validators={[validator1,validator2]} />
<TextAreaWithValidator validators={[validator1,validator2]} />
编辑2: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
HOC
的 props 传递,查看我的编辑以获取另一个演示。 - bob服务不仅限于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组件中,在这种情况下,就像您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可以减少服务的需要,并仅限于使用类似于item 1的可重用辅助函数...
@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"));
注意事项
我和你一样处于同样的情况。在你提到的情况下,我会把输入验证UI组件实现为一个React组件。
我认为验证逻辑本身的实现不应该(必须)耦合。因此,我会将其放入一个单独的JS模块中。
也就是说,对于不应该耦合的逻辑,请使用单独文件中的JS模块/类,并使用require/import来解耦组件与“服务”之间的关系。
这样可以进行依赖注入并对两者进行单元测试。