【发布时间】:2020-09-30 04:50:30
【问题描述】:
我被这个问题困住了,我正在使用 redux 来解决这个问题并将问题分为 4 个部分。我想要实现的是将组件道具与另一个组件(也称为 PropEditor 表单)内的 UI 动态映射。我在说什么,首先看到它还没有实现它只是一个我想要实现的原型。
如果您为我提供更好的解决方案来解决此问题,我将不胜感激。
我的做法:
我有一个名为 Heading.js 的组件,其中包含 2 个道具 hasFruit 一个布尔类型和一个 fruitName 字符串类型。它可以是任何库中的组件,但让我们从简单开始。
src/components/Heading.js
import React from 'react';
export const Heading = (props) => {
const { hasFruit, fruitName } = props;
return <h1>Fruit name will show { hasFruit ? fruitName : 'Oh no!'}</h1>
};
A 部分:输入类型
我想将此组件道具显示为PropEditor 组件上的 UI。所以,我必须为道具定义不同的 UI 组件。所以,我创建了 2 个输入类型组件。
src/editor/components/types/Boolean.js
import React from 'react';
import PropTypes from 'prop-types';
const propTypes = {
/** object for the boolean input type. */
prop: PropTypes.shape({
/** It will be the name of the prop. */
name: PropTypes.string,
/** It will be the value of the prop. */
value: PropTypes.bool,
}),
/** onChange handler for the input */
onChange: PropTypes.func
};
const defaultProps = {
prop: {},
onChange: (value) => value,
};
const Boolean = (props) => {
const { prop, onChange } = props;
return (
<input
id={prop.name}
name={prop.name}
type="checkbox"
onChange={(event) => onChange(event.target.checked)}
checked={prop.value}
/>
);
};
Boolean.propTypes = propTypes;
Boolean.defaultProps = defaultProps;
export default Boolean;
src/editor/components/types/Text.js
import React from 'react';
import PropTypes from 'prop-types';
const propTypes = {
/** object for the text input type. */
prop: PropTypes.shape({
/** It will be the name of the prop. */
name: PropTypes.string,
/** It will be the value of the prop. */
value: PropTypes.string
}),
/** onChange handler for the input */
onChange: PropTypes.func
};
const defaultProps = {
prop: {},
onChange: (value) => value,
};
const Text = (props) => {
const { prop, onChange } = props;
const handleChange = (event) => {
const { value } = event.target;
onChange(value);
};
return (
<input
id={prop.name}
type="text"
onChange={handleChange}
value={prop.value}
/>
);
};
Text.propTypes = propTypes;
Text.defaultProps = defaultProps;
export default Text;
稍后我们将在PropForm 组件中导入这些组件,该组件是PropEditor 组件的子组件。所以我们可以映射这些类型。
src/editor/components/types/index.js
import BooleanType from './Boolean';
import TextType from './Text';
export default {
boolean: BooleanType,
text: TextType,
};
B 部分:Redux
整个场景,2个action会派发SET_PROP在store和SET_PROP_VALUE上设置props数据,即当输入改变时通过PropEditor组件派发并更新输入的值。
src/editor/actionTypes:
// PropEditor Actions
// One single prop
export const SET_PROP = 'SET_PROP';
// One single prop value
export const SET_PROP_VALUE = 'SET_PROP_VALUE';
我已经定义了 2 个动作创建者。
src/editor/PropActions.js:
import * as actionTypes from './actionTypes';
// Prop related action creators
/**
* @param prop {Object} - The prop object
* @return {{type: {string}, data: {Object}}}
*/
export const setProp = (prop) => {
return {
type: actionTypes.SET_PROP,
data: prop
};
};
// Prop value related actions
/**
* @param prop {Object} - The prop object
* @return {{type: {string}, data: {Object}}}
*/
export const setPropValue = (prop) => {
return {
type: actionTypes.SET_PROP_VALUE,
data: prop
};
};
src/editor/PropReducer.js:
import * as actionTypes from './actionTypes';
const INITIAL_STATE = {};
export const propReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
// Prop Actions
case (actionTypes.SET_PROP):
const { data } = action;
return { ...state, [data.name]: {...data} };
// Prop Value Actions
case (actionTypes.SET_PROP_VALUE):
return { ...state, [action.data.name]: { ...state[action.data.name], value: action.data.value } };
default:
return state;
}
};
src/editor/PropStore.js:
import { createStore } from 'redux';
import { propReducer } from './PropReducer';
const REDUX_DEV_TOOL = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
export const store = createStore(propReducer, REDUX_DEV_TOOL);
使用 DOM 上的 react-redux 提供程序引导我们的整个 App。
src/index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { store } from './editor/PropStore';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
C部分:主要部分
如何将组件 Heading.js 的 props 与 PropEditor 组件上的 UI 进行映射?
对于这个用户必须用一个高阶组件来包装它的组件,并且在HOC 内部用户必须调用一些函数,这些函数将在幕后帮助我们动态地填充存储。我创建了一些函数,例如 boolean 和 text,它们将调度一个名为 SET_PROP 的操作来填充存储状态。
src/editor/index.js
import { store } from './PropStore';
import { setProp } from './PropActions';
/**
* @param name {string} - The name of the prop
* @param options {Object} - The prop with some additional properties
* @return {*} - Returns the associated value of the prop
*/
const prop = (name, options) => {
const defaultValue = options.value;
// Create an object and merge with additional properties like `defaultValue`
const prop = {
...options,
name,
defaultValue,
};
store.dispatch(setProp(prop));
return defaultValue;
};
/**
* @param name {string} - The name of the prop
* @param value {boolean} - The value of the prop
* @return {boolean} - Returns the value of the prop
*/
export const boolean = (name, value) => {
// Returns the value of the prop
return prop(name, { type: 'boolean', value });
};
/**
* @param name {string} - The name of the prop
* @param value {string} - The value of the prop
* @return {text} - Returns the value of the prop
*/
export const text = (name, value) => {
// Returns the value of the prop
return prop(name, { type: 'text', value });
};
在 DOM 上渲染 HOC 组件和 PropEditor:
src/blocks.js:
import React from 'react';
import { boolean, text } from './editor';
import { Heading } from './components/Heading';
// WithHeading Block
export const WithHeading = () => {
const boolVal = boolean('hasFruit', true);
const textVal = text('fruitName', 'Apple');
return (<Heading hasFruit={boolVal} fruitName={textVal}/>);
};
这是我们的主要App 组件。
src/App.js:
import React from 'react';
import { PropEditor } from './editor/components/PropEditor';
import { WithHeading } from './blocks';
const App = () => {
return (
<div className="App">
{/* PropEditor */}
<PropEditor />
{/* Blocks */}
<WithHeading/>
</div>
);
};
export default App;
D 部分:最终部分 PropEditor 组件
PropEditor 将在任何输入发生更改时调度一个动作,但请记住,我们所有的 props 都被转换为用于呈现 UI 的对象数组,这些对象将在 PropForm 组件中传递。
src/editor/components/PropEditor.js:
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { PropForm } from './PropForm';
import { setPropValue } from '../PropActions';
export const PropEditor = () => {
// Alternative to connect’s mapStateToProps
const props = useSelector(state => {
return state;
});
// Alternative to connect’s mapDispatchToProps
// By default, the return value of `useDispatch` is the standard Dispatch type defined by the
// Redux core types, so no declarations are needed.
const dispatch = useDispatch();
const handleChange = (dataFromChild) => {
dispatch(setPropValue(dataFromChild));
};
// Convert objects into array of objects
const propsArray = Object.keys(props).map(key => {
return props[key];
});
return (
<div>
{/* Editor */}
<div style={styles.editor}>
<div style={styles.container}>
{ propsArray.length === 0
? <h1 style={styles.noProps}>No Props</h1>
: <PropForm props={propsArray} onFieldChange={handleChange} />
}
</div>
</div>
</div>
);
};
src/editor/components/PropForm.js:
import React from 'react';
import PropTypes from 'prop-types';
import TypeMap from './types';
const propTypes = {
props: PropTypes.arrayOf(PropTypes.object).isRequired,
onFieldChange: PropTypes.func.isRequired
};
// InvalidType component
const InvalidType = () => (<span>Invalid Type</span>);
export const PropForm = (properties) => {
/**
* @param name {string} - Name of the prop
* @param type {string} - InputType of the prop
* @return {Function} - Returns a function
*/
const makeChangeHandler = (name, type) => {
const { onFieldChange } = properties;
return (value = '') => {
// `change` will be an object and value will be from the onChange
const change = {name, type, value};
onFieldChange(change);
};
};
// Take props from the component properties
const { props } = properties;
return (
<form>
{
props.map(prop => {
const changeHandler = makeChangeHandler(prop.name, prop.type);
// Returns a component based on the `type`
// if the `type` is boolean then
// return Boolean() component
let InputType = TypeMap[prop.type] || InvalidType;
return (
<div style={{marginBottom: '16px'}} key={prop.name}>
<label htmlFor={prop.name}>{`${prop.name}`}</label>
<InputType prop={prop} onChange={changeHandler}/>
</div>
);
})
}
</form>
);
};
PropForm.propTypes = propTypes;
在所有这些解释之后,我的代码运行良好。
问题是当SET_PROP_VALUE 操作在PropEditor 组件内的输入更改上调度时,Heading 组件的重新渲染没有发生。
正如您在 Redux DevTools 扩展中看到的那样,商店已完美更改,但组件 Heading 的重新渲染没有发生。
我认为是因为在我的 HOC text() 和 boolean() 函数内部没有返回更新的值。
有没有办法解决这个问题?
请不要提及这一点,我必须将我的WithHeading 组件与react-redux 连接起来。我知道这一点,但有没有办法像 boolean('hasFruit', true) 和 text('fruitName', 'Apple') 这样的函数在更新商店状态时返回最新值?
代码沙盒:Sandbox
存储库:Repository
【问题讨论】:
-
如果是
parent-child关系或child-parent关系,则可以在组件之间进行通信,reactjs中无法在两个单独的组件之间进行直接通信。您必须使用另一种模式,使用事件发射器模式在PropManager类上发出事件并在PropEditor内监听,这是一个复杂的过程。相反,使用 redux 来解决这个问题,创建一个 prop store,并调度一个 action 来操作 prop 值。 redux.js.org/introduction/getting-started -
@TimmyMucks 你能给我提供一个关于 redux 或 event-emitter-pattern 的例子吗?
-
@TimmyMucks 我编辑了问题,现在使用
redux,请再次阅读问题。
标签: reactjs redux react-redux