首页 > 解决方案 > React 上下文 API 不会将新状态值传递给所有 Consumer props

问题描述

我正在构建一个站点,其中包含很多反应组件,但不是 SPA 或完整的反应站点。因此反应组件被拉入页面。当各种菜单的打开或下拉菜单出现时,我有一个覆盖 Ui 的覆盖组件。我遇到的问题是我已经像这样设置了我的上下文:

import React, { Component } from 'react';
import PropTypes from 'prop-types';

export const OverlayContext = React.createContext();

export class OverlayProvider extends Component {
    state = {
            showOverlay: true,
            test: 'default test value'
    }

    showHideOverlay = () => {
        const visible = this.state.showOverlay;
        this.setState({
            showOverlay: visible ? false : true
        });
    }

    render() {
        return (
            <OverlayContext.Provider value={{
                state: this.state,
                showHideOverlay: this.showHideOverlay
            }}>
                {this.props.children}
            </OverlayContext.Provider>
        );
    }
}

OverlayProvider.propTypes = {
    children: PropTypes.object
}

然后我将此提供者和消费者传递给 2 个不同的组件:

  1. 我的覆盖组件
  2. 我的 MyAccount 组件。

它们在这里: 叠加层:

import React from 'react';
import { render } from 'react-dom';
import PropTypes from 'prop-types'

import { OverlayProvider, OverlayContext } from './../contexts/OverlayContext';


// Styling
import styled from 'styled-components'
import { colors } from '../global-styled-constants/colors';
import { mixins } from '../global-styled-constants/mixins';
import { sizing } from '../global-styled-constants/sizing';

//Animations
import { fadeIn } from '../global-styled-constants/animations';
import { fadeOut } from '../global-styled-constants/animations';

const PageOverlay = styled.div`
    ${mixins.dimensions('100%', '100%')};
    ${mixins.position('fixed', '0', '0')};
    display: block;
    background: ${colors.red};
    z-index: ${sizing.zIndex2};
    ${props => props.showOverlay ? `animation: ${fadeIn} 1s 0.3s both;` : `animation: ${fadeOut} 1.3s both;`}
`;

// the state containing the count will be fetched via the API and delivered to this component via redux
export const Overlay = () => {
    return (
        <OverlayProvider>
            <OverlayContext.Consumer>
                {(value) => (
                    <PageOverlay showOverlay={value.state.showOverlay}/>
                )}
            </OverlayContext.Consumer>
        </OverlayProvider>
    );
}

render(<Overlay/>, document.getElementById('react-overlay'))


Overlay.propTypes = {
    showOverlay: PropTypes.bool.isRequired
}

我的帐户:

import React, { Component, Fragment } from 'react';
import { OverlayProvider, OverlayContext } from '../../contexts/OverlayContext';

// Styling
import styled from 'styled-components'
import { colors } from '../../global-styled-constants/colors'
import { mixins } from '../../global-styled-constants/mixins'
import { sizing } from '../../global-styled-constants/sizing'
import { type } from '../../global-styled-constants/typography'
// import { mq } from '../../global-styled-constants/mediaQueries';

const Wrapper = styled.div`
    position: relative;
`;

const List = styled.ul`
    ${mixins.dimensions('195px', 'auto')};
    position: absolute;
    background: ${colors.lightSmoke};
    z-index: 10;
    top: 38px;
    padding: 15px;
    ${props => props.showDropdown ? `display: block;` : `display: none`};

`;

const Item = styled.li`
    padding: 15px 0;

    a {
        font-size: ${sizing.scaleh10};
        font-family: ${type.fsSanSerif};
        font-weight: ${sizing.fontWeightBold};
        font-style: normal;
        text-decoration: none;

    }

    &:hover {
        cursor: pointer;
        color: ${colors.orange};
    }
`;

const Trigger = styled.li`
    ${mixins.flex('center')};
    height: 38px;
    font-size: ${sizing.scaleh10};
    padding: 0 20px;
    position: relative;
    color: ${colors.anotherGrey};
    font-family: ${type.fsSanSerif};
    font-weight: ${sizing.fontWeightBold};
    font-style: normal;
    text-decoration: none;
    cursor: pointer;
    ${props => props.showDropdown && `background: ${colors.lightSmoke}; color: ${colors.orange}`};
`;


const accountData = {
    "loggedOut": {
        "displayCTA": "My Account",
        "links": [
            {
                "displayName": "Sign In",
                "link": "www.google.com"
            },
            {
                "displayName": "Sign up / Join",
                "link": "www.google.com"
            },
            {
                "displayName": "Register Products",
                "link": "www.google.com"
            },
            {
                "displayName": "Subscribe to Newsletter",
                "link": "www.google.com"
            }
        ]
    },
    "loggedIn": {
        "displayCTA": "{userEmail}",
        "links": [
            {
                "displayName": "My Products",
                "link": "www.google.com"
            },
            {
                "displayName": "My Wishlist",
                "link": "www.google.com"
            },
            {
                "displayName": "My Orders",
                "link": "www.google.com"
            },
            {
                "displayName": "My Profile",
                "link": "www.google.com"
            },
            {
                "displayName": "Sign Out",
                "link": "www.google.com"
            },
            {
                "displayName": "Register Products",
                "link": "www.google.com"
            },
            {
                "displayName": "Newsletter Sign up",
                "link": "www.google.com"
            }
        ]
    }
}




export default class MyAccount extends Component {
    constructor(props) {
        super(props);
        this.state = {
            showDropdown: false,
            loggedIn: false
        }
    }

    hoverAwakeMenu = () => {
        this.setState({
            showDropdown: true
        })
    }

    mouseLeft = () => {
        this.setState({
            showDropdown: false
        });
    }

    render() {
        const { showDropdown, loggedIn } = this.state;
        const data = loggedIn ? accountData.loggedIn.links : accountData.loggedOut.links;
        const displayCTA = loggedIn ? accountData.loggedIn.displayCTA : accountData.loggedOut.displayCTA;

        const itemsList = data.map((item, i) => <Item key={i}><a href={item.link}>{item.displayName}</a></Item>);

        return (
            <OverlayProvider>
                <Fragment>
                    <Wrapper onMouseLeave={this.mouseLeft}>
                        <OverlayContext.Consumer>
                            {(value) => (
                                <Trigger showDropdown={showDropdown} onMouseEnter={(e) => {this.hoverAwakeMenu(e); value.showHideOverlay(e);}}>{displayCTA}</Trigger>
                            )}
                        </OverlayContext.Consumer>
                        <List showDropdown={showDropdown}>
                            {itemsList}
                        </List>
                    </Wrapper>
                </Fragment>
            </OverlayProvider>
        );
    }
}

当我调用 value.showHideOverlay(e); 从 MyAccount.js 它更新上下文中的状态,但它不会将此更改转发到基于此布尔值的道具“showOverlay”样式的 Overlay.js。如果我在我的帐户中放了一个测试道具,它就会得到改变。如果我从 Overlay.js 触发事件,也会发生同样的事情。覆盖层内的道具从上下文中的状态更新,但不是 MyAccount.js 中的那个

我猜我可能对多个提供者做错了什么,但我的印象是多个提供者对反应上下文 API 绝对没问题。

任何方向表示赞赏!

标签: reactjsreact-context

解决方案


上下文 API 依赖于组件层次结构。一个值的更改Provider不会神奇地出现在另一个值中Provider。在这一点上,它们完全无关。

为了在Consumer组件之间共享一个值,应该有一个Provider包含 all的单个ConsumerOverlayProvider在这一点上是多余的,因为这个组件不应该被重用。

如果这是非 SPA,则应将不相关的组件呈现为相同的门户,如本答案Provider中所建议的那样。


推荐阅读