Je suis bloqué sur ce problème, j'utilise redux pour résoudre ce problème et j'ai divisé la question en 4 parties. Ce que j'essaie de faire est de mapper dynamiquement les props d'un composant avec l'interface utilisateur d'un autre composant (aussi connu sous le nom de PropEditor Form). Ce que je veux dire, c'est que ce n'est pas encore implémenté, c'est juste un prototype que je veux implémenter.
J'apprécierais également que vous me fournissiez une meilleure solution pour résoudre ce problème.
Mon approche :
J'ai un composant nommé Heading.js
qui contient 2 accessoires hasFruit
un type booléen et un fruitName
type de chaîne. Il peut s'agir d'un composant de n'importe quelle bibliothèque, mais commençons par le plus simple.
src/composants/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>
};
Partie A : Types d'entrée
Je souhaite afficher ce composant en tant qu'interface utilisateur sur la page PropEditor
de la composante. Je dois donc définir les différents composants de l'interface utilisateur pour les accessoires. J'ai donc créé 2 composants de type input.
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;
Plus tard, nous importerons ces composants dans PropForm
qui est l'enfant du composant PropEditor
de la composante. Nous pouvons donc mettre en correspondance ces types.
src/editor/components/types/index.js
import BooleanType from './Boolean';
import TextType from './Text';
export default {
boolean: BooleanType,
text: TextType,
};
Partie B : Redux
Tout au long du scénario, 2 actions seront menées SET_PROP
pour définir les données de l'accessoire dans le magasin et SET_PROP_VALUE
c'est-à-dire l'envoi par PropEditor
lorsque l'entrée est modifiée et met à jour la valeur de l'entrée.
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';
J'ai défini deux créateurs d'actions.
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);
Bootstrap de l'ensemble de notre App
avec le react-redux
sur le DOM.
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')
);
Partie C : Partie principale
Comment cartographier un composant Heading.js
avec une interface utilisateur sur le PropEditor
composant ?
Pour ce faire, l'utilisateur doit envelopper son composant dans un composant d'ordre supérieur et à l'intérieur de celui-ci HOC
L'utilisateur doit appeler certaines fonctions qui, dans les coulisses, nous aideront à remplir dynamiquement le magasin. J'ai créé quelques fonctions comme boolean
y text
qui enverra une action nommée SET_PROP
pour remplir l'état du magasin.
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 });
};
Rendre le HOC
et PropEditor
sur le DOM :
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}/>);
};
Il s'agit de notre principal App
de la composante.
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;
Partie D : Partie finale PropEditor
composante
PropEditor
enverra une action lorsqu'une entrée est modifiée, mais n'oubliez pas que toutes nos actions de props
sont convertis en un tableau d'objets pour le rendu de l'interface utilisateur, qui sera passé à l'intérieur de la fonction PropForm
de la composante.
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;
Après toutes ces explications, mon code fonctionne parfaitement.
Le problème est le re-rendement de l'image Heading
ne se produit pas lorsque le composant SET_PROP_VALUE
est déclenchée lors de la modification de l'entrée à l'intérieur de l'élément PropEditor
de la composante.
Le magasin est parfaitement modifié, comme vous pouvez le voir avec le bouton Outils de développement Redux mais le re-rendu du composant Heading
n'a pas lieu.
Je pense que c'est parce qu'à l'intérieur de mon HOC
text()
y boolean()
ne renvoient pas de valeur actualisée.
Existe-t-il un moyen de résoudre ce problème ?
S'il vous plaît, ne le mentionnez pas Je dois connecter mon WithHeading
avec le composant react-redux
. Je sais cela, mais existe-t-il un moyen pour que les fonctions telles que boolean('hasFruit', true)
y text('fruitName', 'Apple')
renvoie la dernière valeur lorsque l'état du magasin est mis à jour ?
Codesandbox : Bac à sable
Dépôt : Référentiel