模型助手属于 React/Flux 的什么地方?
Where do model helpers belong in React/Flux?
在尝试全神贯注于 React 和 Flux 时,我很难决定将我称之为 "model helper" 的方法放在哪里才有意义。
例如,假设 Store 中包含一个 "Person" 实体,并且假设 Person 有一个 "first name" 和一个 "last name",最合乎逻辑的是放一个 "full name" 简单地将两者连接在一起的辅助方法的地方?我的直觉告诉我最好让 "full name" 在商店中,但我不确定。如果是这样,它是在商店中更新此值的操作,还是应该在商店本身内计算?
是否有可接受的地方放置这种功能?
谢谢!
所以商店保存了应用程序的数据和业务逻辑,我认为这个助手就像一个应该在您的商店内发生的动作。您不需要更新全名的操作,一旦名字和第二个名字可用,它应该由商店本身连接起来。
除了@Christian 的回答(我同意)之外,您还可以使用 object-assign
模块在商店中使用通用助手:https://www.npmjs.com/package/object-assign
这是我的一个商店的部分示例,它使用辅助方法(例如 isAuthenticated
和 getUsername
)使用 object-assign
将 StatusMixin
组合到每个商店中:
var AuthStore = assign({}, StatusMixin, EventEmitter.prototype, {
isAuthenticated: function () {
return _data.get(TOKEN_KEY) ? true : false;
},
getUsername() {
return _data.get(USERNAME_KEY);
},
getToken() {
return _data.get(TOKEN_KEY);
},
invalidate() {
_data = _data.clear();
this.setStatus(''); //this method is from the StatusMixin!
this.emitChange(Constants.CHANGED);
},
emitChange: function() {
LocalStorage.set(Constants.ls.AUTH_STORE, {
auth_token: _data.get(TOKEN_KEY),
username: _data.get(USERNAME_KEY)
});
this.emit(Constants.CHANGED);
},
addChangeListener: function(callback) {
this.on(Constants.CHANGED, callback);
},
removeChangeListener: function(callback) {
this.removeListener(Constants.CHANGED, callback);
},
getState: function() {
return _data;
}
});
和(完整)StatusMixin
'use strict';
var logger = require('../../util/Logger');
var StatusMixin = {
_status: '',
getStatus: function() {
return this._status;
},
setStatus(status) {
this._status = status;
}
};
module.exports = StatusMixin;
现在我可以调用 AuthStore.setStatus(Constants.request.PENDING);
(我为每个 Store 都这样做)而无需在每个 Store 上编写 setStatus
方法。
一般来说,"best practice"这里是创建一个高阶组件,它提供辅助函数或连接的全名作为需要此修改值的组件的道具。
function giveFullName(Component) {
const ComponentWithFullName = React.createClass({
render() {
return <Component {...this.props} fullName={this.props.firstName+" "+this.props.lastName} />;
}
});
return ComponentWithFullName;
};
var PersonPage = React.createClass({
render() {
var { name } = this.props.fullName; // get fullName from props
return <div>{'Hello '+(name ? name : 'Mystery Stranger')}</div>;
}
});
PersonPage = ComponentWithFullName(PersonPage)
});
我不同意@cristian 的回答,因为 ReactJS 的优势之一是它强大的关注点分离和易于推理应用程序信息流。如果我们在商店中放置一个辅助方法,那么我们不知道什么时候会看到全名,它是来自商店的全名,还是通过连接同一商店的名字和姓氏自行创建的组件的全名.但是,如果不把这个全名函数放在store中,那么我们就知道任何全名都来自于一个组件。创建可以提供此功能的高阶组件实现相同的 DRY 原则,同时保持清楚地推断 value/UI 元素来自何处的能力。
请参阅 https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750 了解有关 React 中的 HoC 与 Mixins 的更多信息,以及为什么您应该更喜欢 HoC。
为了使事情易于管理,尤其是当您拥有许多商店和大型组件树时,请尝试关注您的商店和组件的功能:
- 存储用于 a) 存储数据(名字、姓氏,非派生数据),以及 b) 为组件提供数据(包括派生数据)。
- 组件用于向用户呈现 a) 数据,以及 b) 与数据交互的锚点。
我会尽量避免在组件树中操作数据。并且会建议任何组件中的任何数据道具始终来自商店。它们是从更高的组件传下来的,但并没有被操纵。
如果辅助函数只处理数据(例如计算一组中的总人数),请将它们放入存储中。
如果他们处理表示逻辑(例如页面上第一人称的字体大小应该更大),请将它们放在一个单独的地方。我将它们放在单独的实用程序中进行导入。
但是只能在尽可能低的组件上调用这些函数。
这样一来,您的代码就更易于维护了。
数据助手和表示逻辑之间有很多灰色地带,所以你在这种情况下的选择很难说。但只要您始终如一地应用自己的逻辑,您的代码就会保持可管理性。
这样,当一个组件给您带来问题时,可以更容易地追踪 props 的来源,或者在您的组件中应用到这些 props 的函数代码。
所以也许是一个具有全名功能的高阶组件,但我不会让高阶组件创建一个新的道具。
在尝试全神贯注于 React 和 Flux 时,我很难决定将我称之为 "model helper" 的方法放在哪里才有意义。
例如,假设 Store 中包含一个 "Person" 实体,并且假设 Person 有一个 "first name" 和一个 "last name",最合乎逻辑的是放一个 "full name" 简单地将两者连接在一起的辅助方法的地方?我的直觉告诉我最好让 "full name" 在商店中,但我不确定。如果是这样,它是在商店中更新此值的操作,还是应该在商店本身内计算?
是否有可接受的地方放置这种功能?
谢谢!
所以商店保存了应用程序的数据和业务逻辑,我认为这个助手就像一个应该在您的商店内发生的动作。您不需要更新全名的操作,一旦名字和第二个名字可用,它应该由商店本身连接起来。
除了@Christian 的回答(我同意)之外,您还可以使用 object-assign
模块在商店中使用通用助手:https://www.npmjs.com/package/object-assign
这是我的一个商店的部分示例,它使用辅助方法(例如 isAuthenticated
和 getUsername
)使用 object-assign
将 StatusMixin
组合到每个商店中:
var AuthStore = assign({}, StatusMixin, EventEmitter.prototype, {
isAuthenticated: function () {
return _data.get(TOKEN_KEY) ? true : false;
},
getUsername() {
return _data.get(USERNAME_KEY);
},
getToken() {
return _data.get(TOKEN_KEY);
},
invalidate() {
_data = _data.clear();
this.setStatus(''); //this method is from the StatusMixin!
this.emitChange(Constants.CHANGED);
},
emitChange: function() {
LocalStorage.set(Constants.ls.AUTH_STORE, {
auth_token: _data.get(TOKEN_KEY),
username: _data.get(USERNAME_KEY)
});
this.emit(Constants.CHANGED);
},
addChangeListener: function(callback) {
this.on(Constants.CHANGED, callback);
},
removeChangeListener: function(callback) {
this.removeListener(Constants.CHANGED, callback);
},
getState: function() {
return _data;
}
});
和(完整)StatusMixin
'use strict';
var logger = require('../../util/Logger');
var StatusMixin = {
_status: '',
getStatus: function() {
return this._status;
},
setStatus(status) {
this._status = status;
}
};
module.exports = StatusMixin;
现在我可以调用 AuthStore.setStatus(Constants.request.PENDING);
(我为每个 Store 都这样做)而无需在每个 Store 上编写 setStatus
方法。
一般来说,"best practice"这里是创建一个高阶组件,它提供辅助函数或连接的全名作为需要此修改值的组件的道具。
function giveFullName(Component) {
const ComponentWithFullName = React.createClass({
render() {
return <Component {...this.props} fullName={this.props.firstName+" "+this.props.lastName} />;
}
});
return ComponentWithFullName;
};
var PersonPage = React.createClass({
render() {
var { name } = this.props.fullName; // get fullName from props
return <div>{'Hello '+(name ? name : 'Mystery Stranger')}</div>;
}
});
PersonPage = ComponentWithFullName(PersonPage)
});
我不同意@cristian 的回答,因为 ReactJS 的优势之一是它强大的关注点分离和易于推理应用程序信息流。如果我们在商店中放置一个辅助方法,那么我们不知道什么时候会看到全名,它是来自商店的全名,还是通过连接同一商店的名字和姓氏自行创建的组件的全名.但是,如果不把这个全名函数放在store中,那么我们就知道任何全名都来自于一个组件。创建可以提供此功能的高阶组件实现相同的 DRY 原则,同时保持清楚地推断 value/UI 元素来自何处的能力。
请参阅 https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750 了解有关 React 中的 HoC 与 Mixins 的更多信息,以及为什么您应该更喜欢 HoC。
为了使事情易于管理,尤其是当您拥有许多商店和大型组件树时,请尝试关注您的商店和组件的功能:
- 存储用于 a) 存储数据(名字、姓氏,非派生数据),以及 b) 为组件提供数据(包括派生数据)。
- 组件用于向用户呈现 a) 数据,以及 b) 与数据交互的锚点。
我会尽量避免在组件树中操作数据。并且会建议任何组件中的任何数据道具始终来自商店。它们是从更高的组件传下来的,但并没有被操纵。
如果辅助函数只处理数据(例如计算一组中的总人数),请将它们放入存储中。 如果他们处理表示逻辑(例如页面上第一人称的字体大小应该更大),请将它们放在一个单独的地方。我将它们放在单独的实用程序中进行导入。 但是只能在尽可能低的组件上调用这些函数。
这样一来,您的代码就更易于维护了。
数据助手和表示逻辑之间有很多灰色地带,所以你在这种情况下的选择很难说。但只要您始终如一地应用自己的逻辑,您的代码就会保持可管理性。
这样,当一个组件给您带来问题时,可以更容易地追踪 props 的来源,或者在您的组件中应用到这些 props 的函数代码。
所以也许是一个具有全名功能的高阶组件,但我不会让高阶组件创建一个新的道具。