我来自angular世界,在那里我可以提取逻辑到服务/工厂,并在我的控制器中使用它们。
我试图了解如何在React应用程序中实现相同的功能。
假设我有一个验证用户密码输入的组件(它的强度)。它的逻辑相当复杂,因此我不想把它写在组件中。
我应该把这个逻辑写在哪里?如果我在商店里使用助焊剂?还是有更好的选择?
我来自angular世界,在那里我可以提取逻辑到服务/工厂,并在我的控制器中使用它们。
我试图了解如何在React应用程序中实现相同的功能。
假设我有一个验证用户密码输入的组件(它的强度)。它的逻辑相当复杂,因此我不想把它写在组件中。
我应该把这个逻辑写在哪里?如果我在商店里使用助焊剂?还是有更好的选择?
当前回答
第一个答案没有反映当前的容器vs呈现者范式。
如果你需要做一些事情,比如验证密码,你可能会有一个函数来做这件事。你会把这个函数作为道具传递给你的可重用视图。
容器
因此,正确的方法是编写一个ValidatorContainer,它将该函数作为属性,并将表单包装在其中,将正确的道具传递给子对象。当涉及到视图时,验证器容器包装视图,视图使用容器逻辑。
验证可以在容器的属性中完成,但如果你使用第三方验证器,或任何简单的验证服务,你可以将该服务作为容器组件的属性,并在容器的方法中使用它。我已经为restful组件做过这样的操作,效果非常好。
供应商
如果需要进行更多的配置,则可以使用提供者/消费者模型。提供者是一种高级组件,它包装在顶层应用程序对象(您要挂载的对象)附近和下面的某个地方,并向上下文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存储区之上定义服务对象。在存储创建过程中,将服务注入中间件,中间件处理影响该服务的任何操作。
通过这种方式,我可以将rest .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.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>
这里有一个简单的例子:
在React世界中,我们有两种类型的逻辑:有状态的和无状态的。现在,这是开始React时要掌握的主要概念。这里我们更新的状态应该是更新UI,而不是Angular对dom的直接更新。两种类型的逻辑是:
That do not depend on state changes, i.e. static logic which doesn't need to re-render something based on state changes. For such cases just create regular js files and import them like a library or helper methods If you have some code that depends on state and u need to resuse it then two options - hocs and the newer hooks. Hooks are a bit hard to wrap our heads around but basically they would force their parent to rerender if their internal state changes so any stateful logic can be defined and reused in different components, and each hook instance would have its own isolated scope.
理解状态和声明性组件需要一点思维转变,但可以在评论中随意提出后续问题
我需要一些可以在多个组件间共享的格式化逻辑,而作为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);
}
如果你还在寻找像Angular这样的服务,你可以尝试react-rxbuilder库
您可以使用@Injectable来注册服务,然后您可以使用useService或CountService。在组件中使用服务
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"));
预防措施
取决于rxjs和typescript 不能在服务中使用箭头函数
请记住,React的目的是更好地耦合逻辑上应该耦合的东西。如果您正在设计一个复杂的“验证密码”方法,它应该耦合在哪里?
每次用户需要输入新密码时,你都需要用到它。这可能出现在注册屏幕、“忘记密码”屏幕、管理员“为其他用户重置密码”屏幕等。
但在这些情况下,它总是会绑定到某个文本输入域。这就是它们应该耦合的地方。
制作一个非常小的React组件,只包含一个输入字段和相关的验证逻辑。在所有可能需要密码输入的表单中输入该组件。
这本质上与为逻辑提供服务/工厂的结果相同,但您将其直接耦合到输入。因此,现在您永远不需要告诉该函数在哪里查找它的验证输入,因为它是永久地绑定在一起的。