Ven*_*son 15 reactjs redux react-redux
我被这个问题困住了,我正在使用 redux 来解决这个问题,并将问题分为 4 个部分。我想要实现的是使用另一个组件(也称为 PropEditor Form)内的 UI 动态映射组件道具。我在说什么,首先看到它尚未实现,但它只是我想要实现的原型。
如果您为我提供更好的解决方案来解决这个问题,我也将不胜感激。
我的做法:
我有一个名为的组件Heading.js,其中包含 2 个道具,hasFruit一个布尔类型和一个fruitName字符串类型。它可以是任何库中的组件,但让我们从简单开始。
源代码/组件/标题.js
import React from 'react';
export const Heading = (props) => {
const { hasFruit, fruitName } = props;
return <h1>Fruit name will show { hasFruit ? fruitName : 'Oh no!'}</h1>
};
Run Code Online (Sandbox Code Playgroud)
A 部分:输入类型
我想将此组件道具显示为组件上的 UI PropEditor。所以,我必须为道具定义不同的 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;
Run Code Online (Sandbox Code Playgroud)
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;
Run Code Online (Sandbox Code Playgroud)
稍后我们将在PropForm组件的子组件组件中导入这些PropEditor组件。所以我们可以映射这些类型。
src/editor/components/types/index.js
import BooleanType from './Boolean';
import TextType from './Text';
export default {
boolean: BooleanType,
text: TextType,
};
Run Code Online (Sandbox Code Playgroud)
B 部分:还原
在整个场景中,2 个动作将分派SET_PROP以在商店上设置道具数据,SET_PROP_VALUE即PropEditor在输入更改时通过组件分派并更新输入的值。
源代码/编辑器/操作类型:
// PropEditor Actions
// One single prop
export const SET_PROP = 'SET_PROP';
// One single prop value
export const SET_PROP_VALUE = 'SET_PROP_VALUE';
Run Code Online (Sandbox Code Playgroud)
我定义了 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
};
};
Run Code Online (Sandbox Code Playgroud)
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;
}
};
Run Code Online (Sandbox Code Playgroud)
源代码/编辑器/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);
Run Code Online (Sandbox Code Playgroud)
App使用react-reduxDOM 上的提供程序引导我们的整体。
源代码/索引.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')
);
Run Code Online (Sandbox Code Playgroud)
C部分:主要部分
如何将组件Heading.js道具与组件上的 UI映射PropEditor?
为此,用户必须用高阶组件包装其组件,并且在该HOC用户内部必须调用一些函数,这些函数将在幕后帮助我们动态填充商店。我创建了一些函数,比如boolean和text,它们将调度一个名为SET_PROP填充商店状态的动作。
源代码/编辑器/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 });
};
Run Code Online (Sandbox Code Playgroud)
在 DOM 上渲染HOC组件PropEditor:
源代码/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}/>);
};
Run Code Online (Sandbox Code Playgroud)
这是我们的主要App组成部分。
源代码/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;
Run Code Online (Sandbox Code Playgroud)
D 部分:最终部分PropEditor组件
PropEditor将在任何输入更改时调度一个动作,但请记住我们所有的props都被转换为一个对象数组,用于渲染将在PropForm组件内部传递的 UI 。
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>
);
};
Run Code Online (Sandbox Code Playgroud)
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;
Run Code Online (Sandbox Code Playgroud)
在所有这些解释之后,我的代码运行良好。
问题是Heading当SET_PROP_VALUE对PropEditor组件内部的输入更改分派操作时,不会重新渲染组件。
正如您在Redux DevTools扩展中看到的那样,存储区发生了完美的变化,但组件的重新渲染Heading并未发生。
我认为是因为在 myHOC text()和boolean()函数内部没有返回更新的值。
有没有办法解决这个问题?
请不要提及这一点,我必须将我的WithHeading组件与react-redux. 我知道这一点,但是有没有办法在更新商店状态时函数喜欢boolean('hasFruit', true)并text('fruitName', 'Apple')返回最新值?
存储库:存储库
这里我创建了 4 个演示,每个演示都是前一个演示的扩展版本:
1)通过mapStateToProps连接sore和update组件
2)通过使用useSelector
const boolVal = useSelector(state => state.hasFruit ? state.hasFruit.value : false );
Run Code Online (Sandbox Code Playgroud)
3)将动态名称传递给useSelector
const booleanVal = useSelector(state => booleanSelector(state, "hasFruit"));
Run Code Online (Sandbox Code Playgroud)
4)创建一个自定义钩子,这样您只需传递名称即可获取更新的值
const booleanVal = useGetValueFromStore("hasFruit");
Run Code Online (Sandbox Code Playgroud)
问题是标题组件没有重新渲染
原因 :
是的,因为它没有连接到商店,它如何知道正在进行一些更改store,您需要致电connect与商店建立连接并了解最新的更改。
这是更新后的代码blocks.js:
// WithHeading Block
const WithHeading = props => {
useEffect(() => {
boolean("hasFruit", true); // <--- Setting initial value
text("fruitName", "Apple"); // <--- Setting initial value
}, []); // <----- get called only on mount
return <Heading hasFruit={props.boolVal} fruitName={props.textVal} />;
};
// to get updated state values inside the component as props
const mapStateToProps = state => {
return {
boolVal: state.hasFruit ? state.hasFruit.value : false,
textVal: state.fruitName ? state.fruitName.value : ""
};
};
// to make connection with store
export default connect(mapStateToProps)(WithHeading);
Run Code Online (Sandbox Code Playgroud)
1) 工作演示:
另一种方法是您可以使用useSelector:
// WithHeading Block
const WithHeading = props => {
// console.log(props);
const boolVal = useSelector(state =>
state.hasFruit ? state.hasFruit.value : false
);
const textVal = useSelector(state =>
state.fruitName ? state.fruitName.value : ""
);
useEffect(() => {
boolean("hasFruit", true);
text("fruitName", "Apple");
}, []);
return <Heading hasFruit={boolVal} fruitName={textVal} />;
};
export default WithHeading;
Run Code Online (Sandbox Code Playgroud)
2) 工作演示:
您还可以将选择器放在单独的文件中,以便您可以随时使用它
const WithHeading = props => {
// you can pass the input names here, and get value of it
const booleanVal = useSelector(state => booleanSelector(state, "hasFruit"));
const textVal = useSelector(state => textValSelector(state, "fruitName"));
useEffect(() => {
boolean("hasFruit", true);
text("fruitName", "Apple");
}, []);
return <Heading hasFruit={booleanVal} fruitName={textVal} />;
};
Run Code Online (Sandbox Code Playgroud)
3)工作演示:
自定义 Hook 使用useSelector:
// a function that will return updated value of given name
const useGetValueFromStore = name => {
const value = useSelector(state => (state[name] ? state[name].value : ""));
return value;
};
// WithHeading Block
const WithHeading = props => {
//------- all you need is just to pass the name --------
const booleanVal = useGetValueFromStore("hasFruit");
const textVal = useGetValueFromStore("fruitName");
useEffect(() => {
boolean("hasFruit", true);
text("fruitName", "Apple");
}, []);
return <Heading hasFruit={booleanVal} fruitName={textVal} />;
};
export default WithHeading;
Run Code Online (Sandbox Code Playgroud)
4) 工作演示:
| 归档时间: |
|
| 查看次数: |
1621 次 |
| 最近记录: |