当 store 状态改变时重新渲染组件

Ven*_*son 15 reactjs redux react-redux

我被这个问题困住了,我正在使用 redux 来解决这个问题,并将问题分为 4 个部分。我想要实现的是使用另一个组件(也称为 PropEditor Form)内的 UI 动态映射组件道具。我在说什么,首先看到它尚未实现,但它只是我想要实现的原型。

PropEditor 原型

如果您为我提供更好的解决方案来解决这个问题,我也将不胜感激。

我的做法:

我有一个名为的组件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 商店调试

正如您在Redux DevTools扩展中看到的那样,存储区发生了完美的变化,但组件的重新渲染Heading并未发生。

我认为是因为在 myHOC text()和boolean()函数内部没有返回更新的值。

有没有办法解决这个问题?

请不要提及这一点,我必须将我的WithHeading组件与react-redux. 我知道这一点,但是有没有办法在更新商店状态时函数喜欢boolean('hasFruit', true)并text('fruitName', 'Apple')返回最新值?

代码沙盒:沙盒

存储库:存储库

Viv*_*shi 9

这里我创建了 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) 工作演示:

编辑#SO-redux-connect


另一种方法是您可以使用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) 工作演示:

编辑#SO-redux-connect2

您还可以将选择器放在单独的文件中,以便您可以随时使用它

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)工作演示:

编辑#SO-redux-connect3

自定义 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) 工作演示:

编辑#SO-redux-connect4