我来自一个有角的世界,在那里我可以将逻辑提取到服务/工厂,并在控制器中使用它们。
我试图了解如何在React应用程序中实现相同的目标。
假设我有一个可以验证用户密码输入(强度)的组件。它的逻辑非常复杂,因此我不想将其编写在自己的组件中。
我应该在哪里写这个逻辑?在商店中,如果我使用助焊剂?还是有更好的选择?
我来自一个有角的世界,在那里我可以将逻辑提取到服务/工厂,并在控制器中使用它们。
我试图了解如何在React应用程序中实现相同的目标。
假设我有一个可以验证用户密码输入(强度)的组件。它的逻辑非常复杂,因此我不想将其编写在自己的组件中。
我应该在哪里写这个逻辑?在商店中,如果我使用助焊剂?还是有更好的选择?
Answers:
第一个答案并不反映当前的Container vs Presenter范例。
如果您需要执行某些操作(例如验证密码),则可以使用执行此操作的功能。您将把该函数作为道具传递给可重用的视图。
因此,正确的方法是编写一个ValidatorContainer,它将具有该功能的属性,并将其包装在其中,然后将正确的道具传递给孩子。当涉及到视图时,验证器容器将包装您的视图,而视图将使用容器逻辑。
验证可以全部在容器的属性中完成,但是如果您使用的是第三方验证器或任何简单的验证服务,则可以将该服务用作容器组件的属性,并在容器的方法中使用它。我已经为宁静的组件完成了此任务,并且效果很好。
如果需要更多配置,则可以使用提供者/消费者模型。提供程序是一个高级组件,它包装在顶层应用程序对象(您安装的对象)附近和下方,并将其自身的一部分或在顶层配置的属性提供给上下文API。然后,我将容器元素设置为使用上下文。
父/子上下文关系不必彼此靠近,只是必须以某种方式将子代降落。Redux存储和React Router以这种方式起作用。我用它为我的rest容器提供了一个根源的rest上下文(如果我不提供自己的)。
(请注意:上下文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)
}
}
当您意识到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文件。
您可能需要更复杂的服务,具体取决于其他服务。在这种情况下,您的服务文件可能会返回类构造函数而不是静态对象,因此您可以自己在组件中创建该对象的实例。您也可以考虑实现一个简单的单例,以确保整个应用程序中始终只使用一个服务对象实例。
我需要一些格式化逻辑在多个组件之间共享,并且作为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.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,没有一种简单的方法可以通过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经验。考虑一下逻辑的性质:
有些还可以重用HOC,但对我来说,以上内容涵盖了几乎所有用例。另外,考虑使用鸭子来扩展状态管理,以使关注点分离并以用户界面为中心。
我也来自Angular并尝试React,到目前为止,一种推荐的(?)方式似乎正在使用High-Order Components:
高阶组件(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
s 共享由多个验证函数组成的逻辑,例如:
<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
HOC
,有关其他演示,请参见我的编辑。
服务不限于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函数 ...这就像一个大商店可以跟踪您的状态并可以在您的各个组件之间共享状态,因此可以消除我们在服务中使用的吸气剂安装工的许多麻烦...
做一个DRY代码而不重复需要使代码可重用和可读的东西总是很好的,但是不要尝试在React应用程序中遵循Angular的方法像第4条中提到的那样,使用Redux可以减少对服务,并且您限制将它们用于某些可重用的辅助功能,例如项目1 ...
我遇到的最常用的可重用逻辑模式是编写钩子或创建utils文件。这取决于您要完成的工作。
hooks/useForm.js
就像您要验证表单数据一样,我将创建一个名为useForm.js的自定义钩子,并提供表单数据,作为回报,它将返回一个包含以下内容的对象:
Object: {
value,
error,
}
您一定可以从中获得更多收益。
utils/URL.js
另一个示例就像您要从URL提取一些信息,然后为该文件创建一个包含函数的utils文件,并在需要时将其导入:
export function getURLParam(p) {
...
}