如何制作可重用的 React/MobX 组件?
How to make reusable React/MobX components?
我看到的将 React 组件与 MobX 存储集成的示例似乎是紧密耦合的。我想以一种更可重用的方式来做到这一点,并希望帮助理解 "right" 的方式来做到这一点。
我编写了以下代码(React + MobX + Typescript)来说明我想做什么以及我 运行 遇到的问题。
商店有多个可观察的时间戳。
/***
* Initialize simple store
*/
class MyStore {
@observable value: number;
@action setValue(val: number) { this.value = val; }
@observable startTimestamp: number;
@action setStartTimestamp(val: number) { this.startTimestamp = val; }
@observable endTimestamp: number;
@action setEndTimestamp(val: number) { this.endTimestamp = val; }
}
假设我想制作一个可重复使用的日期输入组件,允许用户为 startTimestamp、endTimestamp 或其他一些商店输入日期 属性。更一般地说,我想创建一个组件,我可以用它来修改任何商店的任意 属性。
我对 React/MobX 集成的最佳理解是组件接收一个 MobX 存储,读取存储的可观察属性,并可能执行修改这些属性的操作。然而,这似乎假设组件连接到商店属性的名称,这使得它们不能完全重用。
我已经尝试使用以下 "proxy store" 方法将我想要的 属性 暴露给组件 "value":
class MyStoreTimestampProxy {
constructor(private store: MyStore, private propertyName: 'startTimestamp' | 'endTimestamp') {
}
@observable get value() {
return this.store[this.propertyName];
}
@action setValue(val: number) {
this.store[this.propertyName] = val;
}
};
const myStoreStartTimestamp = new MyStoreTimestampProxy(myStore, 'startTimestamp');
const myStoreEndTimestamp = new MyStoreTimestampProxy(myStore, 'endTimestamp');
但是,我觉得我做事的方式有点不对 React/MobX,我想了解此处的最佳做法。谢谢!
完整代码如下:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { observable, action } from 'mobx';
import { observer } from 'mobx-react';
/***
* Initialize simple store
*/
class MyStore {
@observable value: number;
@action setValue(val: number) { this.value = val; }
@observable startTimestamp: number;
@action setStartTimestamp(val: number) { this.startTimestamp = val; }
@observable endTimestamp: number;
@action setEndTimestamp(val: number) { this.endTimestamp = val; }
}
const myStore = new MyStore();
myStore.setValue(new Date().getTime());
/**
* My time input component. Takes in a label for display, and a store for reading/writing the property.
*/
interface IDateInputProps {
label: string;
store: {
value: number;
setValue(val: number): void;
}
}
interface IDateInputState {
value: string;
}
@observer class DateInput extends React.Component<IDateInputProps, IDateInputState> {
constructor(props: IDateInputProps) {
super(props);
this.state = { value: new Date(props.store.value).toDateString() };
}
render() {
return (
<div>
<label>{this.props.label}
<input
value={this.state.value}
onChange={this.onChange.bind(this)} />
</label>
</div>
);
}
onChange(event) {
const date = new Date(event.target.value);
this.setState({ value: event.target.value });
this.props.store.setValue(date.getTime());
}
}
/**
* Test view
*
*/
class TestView extends React.Component {
render() {
return (
<div>
<DateInput label="Editing the value property of store: " store={myStore}></DateInput>
{/* How to create components for startTimestamp and endTimestamp */}
</div>
);
}
};
ReactDOM.render(<TestView />, document.getElementById('root'));
主要问题是您的 DateInput
组件中有 store
的状态依赖项,这使得它很难重用。您需要的是打破这些引用,而不是直接从可重用组件访问商店引用,您必须在父级的 props 中提供它们。
我们走吧。
首先,如果我理解得很好,我认为如果您这样修改您的商店,您的问题会更容易:
class MyStore {
@observable state = {
dateInputsValues : [
{ value: '01/01/1970', label: 'value'},
{ value: '01/01/1970', label: 'startTimestamp' },
{ value: '01/01/1970', label: 'endTimestamp' }
]
}
}
现在,您将能够在 DateInput
中循环 dateInputsValues
并避免代码重复。
那么,与其传递整个商店,不如传递带有您需要的可观察值(即标签和值)的道具?
@observer
class TestView extends React.Component {
render() {
return (
<div>
{myStore.state.dateInputsValues.map(date =>
<DateInput
label={`Editing the ${date.label} property of store: `}
value={date.value}
/>
}
</div>
);
}
};
打破对 DateInput
中商店的旧引用(如您所说,使组件 "tightly coupled" 成为商店并使其几乎不可重用)。用我们添加的道具替换它们。
删除 DateInput
内部 state
。在实际代码中,暂时不需要组件内部状态。这种场景可以直接使用state store。
最后,添加一个 action
方法来修改 value
属性,因为你似乎处于 MobX 严格模式(否则,你可以将值设置在 action
之外)
@observer
class DateInput extends React.Component<IDateInputProps, IDateInputState> {
render() {
return (
<div>
<label>{this.props.label}
<input
value={this.props.value}
onChange={this.onChange} />
</label>
</div>
);
}
onChange = event => {
const date = new Date(event.target.value);
this.setDateInputValue(date.getTime());
}
@action
setDateInputValue = val => this.props.value = val
}
我看到的将 React 组件与 MobX 存储集成的示例似乎是紧密耦合的。我想以一种更可重用的方式来做到这一点,并希望帮助理解 "right" 的方式来做到这一点。
我编写了以下代码(React + MobX + Typescript)来说明我想做什么以及我 运行 遇到的问题。
商店有多个可观察的时间戳。
/***
* Initialize simple store
*/
class MyStore {
@observable value: number;
@action setValue(val: number) { this.value = val; }
@observable startTimestamp: number;
@action setStartTimestamp(val: number) { this.startTimestamp = val; }
@observable endTimestamp: number;
@action setEndTimestamp(val: number) { this.endTimestamp = val; }
}
假设我想制作一个可重复使用的日期输入组件,允许用户为 startTimestamp、endTimestamp 或其他一些商店输入日期 属性。更一般地说,我想创建一个组件,我可以用它来修改任何商店的任意 属性。
我对 React/MobX 集成的最佳理解是组件接收一个 MobX 存储,读取存储的可观察属性,并可能执行修改这些属性的操作。然而,这似乎假设组件连接到商店属性的名称,这使得它们不能完全重用。
我已经尝试使用以下 "proxy store" 方法将我想要的 属性 暴露给组件 "value":
class MyStoreTimestampProxy {
constructor(private store: MyStore, private propertyName: 'startTimestamp' | 'endTimestamp') {
}
@observable get value() {
return this.store[this.propertyName];
}
@action setValue(val: number) {
this.store[this.propertyName] = val;
}
};
const myStoreStartTimestamp = new MyStoreTimestampProxy(myStore, 'startTimestamp');
const myStoreEndTimestamp = new MyStoreTimestampProxy(myStore, 'endTimestamp');
但是,我觉得我做事的方式有点不对 React/MobX,我想了解此处的最佳做法。谢谢!
完整代码如下:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { observable, action } from 'mobx';
import { observer } from 'mobx-react';
/***
* Initialize simple store
*/
class MyStore {
@observable value: number;
@action setValue(val: number) { this.value = val; }
@observable startTimestamp: number;
@action setStartTimestamp(val: number) { this.startTimestamp = val; }
@observable endTimestamp: number;
@action setEndTimestamp(val: number) { this.endTimestamp = val; }
}
const myStore = new MyStore();
myStore.setValue(new Date().getTime());
/**
* My time input component. Takes in a label for display, and a store for reading/writing the property.
*/
interface IDateInputProps {
label: string;
store: {
value: number;
setValue(val: number): void;
}
}
interface IDateInputState {
value: string;
}
@observer class DateInput extends React.Component<IDateInputProps, IDateInputState> {
constructor(props: IDateInputProps) {
super(props);
this.state = { value: new Date(props.store.value).toDateString() };
}
render() {
return (
<div>
<label>{this.props.label}
<input
value={this.state.value}
onChange={this.onChange.bind(this)} />
</label>
</div>
);
}
onChange(event) {
const date = new Date(event.target.value);
this.setState({ value: event.target.value });
this.props.store.setValue(date.getTime());
}
}
/**
* Test view
*
*/
class TestView extends React.Component {
render() {
return (
<div>
<DateInput label="Editing the value property of store: " store={myStore}></DateInput>
{/* How to create components for startTimestamp and endTimestamp */}
</div>
);
}
};
ReactDOM.render(<TestView />, document.getElementById('root'));
主要问题是您的 DateInput
组件中有 store
的状态依赖项,这使得它很难重用。您需要的是打破这些引用,而不是直接从可重用组件访问商店引用,您必须在父级的 props 中提供它们。
我们走吧。
首先,如果我理解得很好,我认为如果您这样修改您的商店,您的问题会更容易:
class MyStore {
@observable state = {
dateInputsValues : [
{ value: '01/01/1970', label: 'value'},
{ value: '01/01/1970', label: 'startTimestamp' },
{ value: '01/01/1970', label: 'endTimestamp' }
]
}
}
现在,您将能够在 DateInput
中循环 dateInputsValues
并避免代码重复。
那么,与其传递整个商店,不如传递带有您需要的可观察值(即标签和值)的道具?
@observer
class TestView extends React.Component {
render() {
return (
<div>
{myStore.state.dateInputsValues.map(date =>
<DateInput
label={`Editing the ${date.label} property of store: `}
value={date.value}
/>
}
</div>
);
}
};
打破对 DateInput
中商店的旧引用(如您所说,使组件 "tightly coupled" 成为商店并使其几乎不可重用)。用我们添加的道具替换它们。
删除 DateInput
内部 state
。在实际代码中,暂时不需要组件内部状态。这种场景可以直接使用state store。
最后,添加一个 action
方法来修改 value
属性,因为你似乎处于 MobX 严格模式(否则,你可以将值设置在 action
之外)
@observer
class DateInput extends React.Component<IDateInputProps, IDateInputState> {
render() {
return (
<div>
<label>{this.props.label}
<input
value={this.props.value}
onChange={this.onChange} />
</label>
</div>
);
}
onChange = event => {
const date = new Date(event.target.value);
this.setDateInputValue(date.getTime());
}
@action
setDateInputValue = val => this.props.value = val
}