(新)来自嵌套组件的反应上下文不起作用
(New) React Context from a Nested Component not working
我在使用 "new" React 上下文 ( https://reactjs.org/docs/context.html ) 时遇到严重问题,无法像文档中的 want/expect 那样工作。我正在使用 React v.16.8.6(升级可能需要很长时间,这是一个很大的应用程序)。我知道新旧东西有些混杂,但请不要拘泥于此..
我这样做是为了尽可能灵活,但它不起作用。
问题是,当涉及到contextAddToCart(..)
时,它只执行空函数而不是我在文档this.addToCart
中定义的状态.我在其他地方也有消费者。似乎它可能以错误的顺序执行此操作。或者每次组件导入时 MinicartContext
它都会重置为空 fn.. 我不知道如何解决这个问题..
我只是 post 我认为最能解释它的相关代码:
webpack.config.js:
const APP_DIR = path.resolve(__dirname, 'src/');
module.exports = function config(env, argv = {}) {
return {
resolve: {
extensions: ['.js', '.jsx'],
modules: [
path.resolve(__dirname, 'src/'),
'node_modules',
],
alias: {
contexts: path.resolve(__dirname, './src/contexts.js'),
},
contexts.js
import React from 'react';
export const MinicartContext = React.createContext({
addToCart: () => {},
getState: () => {},
});
MinicartContainer.jsx
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
MinicartContext,
} from 'contexts';
export default class MinicartContainer extends Component {
constructor(props) {
super(props);
this.addToCart = (product, qty) => {
const { prices } = product;
const { grandTotal, qtyTotal } = this.state;
this.setState({
grandTotal: grandTotal + prices.price,
qtyTotal: qtyTotal + qty,
});
};
this.state = {
grandTotal: -1,
qtyTotal: -1,
currencyCode: '',
addToCart: this.addToCart,
};
}
render() {
const { children } = this.props;
return (
<MinicartContext.Provider value={this.state}>
{children}
</MinicartContext.Provider>
);
}
Header.jsx:
import React, { Component } from 'react';
import {
MinicartContext,
} from 'contexts';
class Header extends Component {
render() {
return (
<div>
<MinicartContainer MinicartContext={MinicartContext}>
<Minicart MinicartContext={MinicartContext} />
</MinicartContainer MinicartContext={MinicartContext}>
{/* stuff */}
<MinicartContainer MinicartContext={MinicartContext}>
<Minicart MinicartContext={MinicartContext} />
</MinicartContainer MinicartContext={MinicartContext}>
</div>
)
}
}
export default Header;
AddToCartButton.jsx
import {
MinicartContext,
} from 'contexts';
export default class AddToCartButton extends Component {
addToCart(e, contextAddToCart) {
e.preventDefault();
const QTY = 1;
const { product, active } = this.props;
// doing stuff ...
contextAddToCart(product, QTY);
}
render() {
return (
<React.Fragment>
<MinicartContext.Consumer>
{({context, addToCart}) => (
<div
onClick={(e) => { this.addToCart(e, addToCart); }}
在我看来,您还没有完全理解 API 单词的上下文。
这是我的 context 的 HOC 实现,也许它可以帮助你更好地理解它是如何工作的。
export const MinicartContext = React.createContext({}) // Export the Context so we can use the Consumer in class and functional components (above). Don't use the Provider from here.
// Wrap the provider to add some custom values.
export const MinicartProvider = props => {
const addToCart = () => {
//Add a default version here
};
const getState = () => {
//Add a default version here
};
// Get the custom values and override with instance ones.
const value = {addToCart, getState, ...props.value}
return <MinicartContext.Provider value={value}>
{props.children}
</MinicartContext.Provider>
}
然后在使用提供商时:
const SomeComponent = props => {
const addToCart = () => {
//A custom version used only in this component, that need to override the default one
};
//Use the Wrapper, forget the MinicartContext.Provider
return <MinicartProvider value={{addToCart}}>
/* Stuff */
</MinicartProvider>
}
使用消费者时,您有以下三种选择:
Class 具有单一上下文的组件
export default class AddToCartButton extends Component {
static contextType = MinicartContext;
render (){
const {addToCart, getState} = this.context;
return (/*Something*/)
}
}
Class 具有多个上下文的组件
export default class AddToCartButton extends Component {
render (){
return (
<MinicartContext.Consumer>{value => {
const {addToCart, getState} = value
return (/*Something*/)
}}</MinicartContext.Consumer>
)
}
}
功能组件
const AddToCartButton = props => {
const {addToCart, getState} = useContext(MinicartContext);
}
您也可以将 Wrapper Provider 创建为 class 组件,并将完整状态作为值传递,但这是不必要的复杂性。
我建议您查看有关上下文的 this guide,并且避免在同一范围内使用相同的名称...您的 AddToCartButton.jsx 文件确实令人困惑:P
我遇到的问题是我在多个地方使用了 <MinicartContainer>
,但所有地方都应该作为同一个。更改它以便它包装所有元素,使其他元素在上下文更新时重置它们的状态。
所以我找到的唯一解决方案是在 MinicartContainer
中制作所有内容 static
(包括状态),并跟踪所有实例,然后对所有实例使用 forceUpdate()
(需要) 实例。 (因为我从不做 this.setState
否则什么都不会更新)
虽然新的 React 上下文可以完全替代 Redux 之类的东西,但就目前而言,它更像是一个非常模糊的规范,可以(有时)以非标准方式替代 Redux。
如果你可以用一个单个提供者组件包装所有子Consumer
s,没有任何副作用,那么你可以使它成为一个更干净的实现。也就是说,我不认为我所做的有任何不好,但不是人们期望干净的实现应该是什么样子。此外,文档中也根本没有提到这种方法。
除了 Toug 的回答外,我还会记住提供者公开的 value
道具。否则它会 re-render 它每次都是订阅者,即使状态没有改变。
export const MinicartContext = React.createContext({}) // Export the Context so we can use the Consumer in class and functional components (above). Don't use the Provider from here.
// Wrap the provider to add some custom values.
export const MinicartProvider = props => {
const addToCart = () => {
//Add a default version here
};
const getState = () => {
//Add a default version here
};
// Get the custom values and override with instance ones.
const value = useMemo(
() => ({addToCart, getState, ...props.value}),
[addToCart, getState, props.value]
);
return <MinicartContext.Provider value={value}>
{props.children}
</MinicartContext.Provider>
}
我在使用 "new" React 上下文 ( https://reactjs.org/docs/context.html ) 时遇到严重问题,无法像文档中的 want/expect 那样工作。我正在使用 React v.16.8.6(升级可能需要很长时间,这是一个很大的应用程序)。我知道新旧东西有些混杂,但请不要拘泥于此..
我这样做是为了尽可能灵活,但它不起作用。
问题是,当涉及到contextAddToCart(..)
时,它只执行空函数而不是我在文档this.addToCart
中定义的状态.我在其他地方也有消费者。似乎它可能以错误的顺序执行此操作。或者每次组件导入时 MinicartContext
它都会重置为空 fn.. 我不知道如何解决这个问题..
我只是 post 我认为最能解释它的相关代码:
webpack.config.js:
const APP_DIR = path.resolve(__dirname, 'src/');
module.exports = function config(env, argv = {}) {
return {
resolve: {
extensions: ['.js', '.jsx'],
modules: [
path.resolve(__dirname, 'src/'),
'node_modules',
],
alias: {
contexts: path.resolve(__dirname, './src/contexts.js'),
},
contexts.js
import React from 'react';
export const MinicartContext = React.createContext({
addToCart: () => {},
getState: () => {},
});
MinicartContainer.jsx
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
MinicartContext,
} from 'contexts';
export default class MinicartContainer extends Component {
constructor(props) {
super(props);
this.addToCart = (product, qty) => {
const { prices } = product;
const { grandTotal, qtyTotal } = this.state;
this.setState({
grandTotal: grandTotal + prices.price,
qtyTotal: qtyTotal + qty,
});
};
this.state = {
grandTotal: -1,
qtyTotal: -1,
currencyCode: '',
addToCart: this.addToCart,
};
}
render() {
const { children } = this.props;
return (
<MinicartContext.Provider value={this.state}>
{children}
</MinicartContext.Provider>
);
}
Header.jsx:
import React, { Component } from 'react';
import {
MinicartContext,
} from 'contexts';
class Header extends Component {
render() {
return (
<div>
<MinicartContainer MinicartContext={MinicartContext}>
<Minicart MinicartContext={MinicartContext} />
</MinicartContainer MinicartContext={MinicartContext}>
{/* stuff */}
<MinicartContainer MinicartContext={MinicartContext}>
<Minicart MinicartContext={MinicartContext} />
</MinicartContainer MinicartContext={MinicartContext}>
</div>
)
}
}
export default Header;
AddToCartButton.jsx
import {
MinicartContext,
} from 'contexts';
export default class AddToCartButton extends Component {
addToCart(e, contextAddToCart) {
e.preventDefault();
const QTY = 1;
const { product, active } = this.props;
// doing stuff ...
contextAddToCart(product, QTY);
}
render() {
return (
<React.Fragment>
<MinicartContext.Consumer>
{({context, addToCart}) => (
<div
onClick={(e) => { this.addToCart(e, addToCart); }}
在我看来,您还没有完全理解 API 单词的上下文。
这是我的 context 的 HOC 实现,也许它可以帮助你更好地理解它是如何工作的。
export const MinicartContext = React.createContext({}) // Export the Context so we can use the Consumer in class and functional components (above). Don't use the Provider from here.
// Wrap the provider to add some custom values.
export const MinicartProvider = props => {
const addToCart = () => {
//Add a default version here
};
const getState = () => {
//Add a default version here
};
// Get the custom values and override with instance ones.
const value = {addToCart, getState, ...props.value}
return <MinicartContext.Provider value={value}>
{props.children}
</MinicartContext.Provider>
}
然后在使用提供商时:
const SomeComponent = props => {
const addToCart = () => {
//A custom version used only in this component, that need to override the default one
};
//Use the Wrapper, forget the MinicartContext.Provider
return <MinicartProvider value={{addToCart}}>
/* Stuff */
</MinicartProvider>
}
使用消费者时,您有以下三种选择:
Class 具有单一上下文的组件
export default class AddToCartButton extends Component {
static contextType = MinicartContext;
render (){
const {addToCart, getState} = this.context;
return (/*Something*/)
}
}
Class 具有多个上下文的组件
export default class AddToCartButton extends Component {
render (){
return (
<MinicartContext.Consumer>{value => {
const {addToCart, getState} = value
return (/*Something*/)
}}</MinicartContext.Consumer>
)
}
}
功能组件
const AddToCartButton = props => {
const {addToCart, getState} = useContext(MinicartContext);
}
您也可以将 Wrapper Provider 创建为 class 组件,并将完整状态作为值传递,但这是不必要的复杂性。
我建议您查看有关上下文的 this guide,并且避免在同一范围内使用相同的名称...您的 AddToCartButton.jsx 文件确实令人困惑:P
我遇到的问题是我在多个地方使用了 <MinicartContainer>
,但所有地方都应该作为同一个。更改它以便它包装所有元素,使其他元素在上下文更新时重置它们的状态。
所以我找到的唯一解决方案是在 MinicartContainer
中制作所有内容 static
(包括状态),并跟踪所有实例,然后对所有实例使用 forceUpdate()
(需要) 实例。 (因为我从不做 this.setState
否则什么都不会更新)
虽然新的 React 上下文可以完全替代 Redux 之类的东西,但就目前而言,它更像是一个非常模糊的规范,可以(有时)以非标准方式替代 Redux。
如果你可以用一个单个提供者组件包装所有子Consumer
s,没有任何副作用,那么你可以使它成为一个更干净的实现。也就是说,我不认为我所做的有任何不好,但不是人们期望干净的实现应该是什么样子。此外,文档中也根本没有提到这种方法。
除了 Toug 的回答外,我还会记住提供者公开的 value
道具。否则它会 re-render 它每次都是订阅者,即使状态没有改变。
export const MinicartContext = React.createContext({}) // Export the Context so we can use the Consumer in class and functional components (above). Don't use the Provider from here.
// Wrap the provider to add some custom values.
export const MinicartProvider = props => {
const addToCart = () => {
//Add a default version here
};
const getState = () => {
//Add a default version here
};
// Get the custom values and override with instance ones.
const value = useMemo(
() => ({addToCart, getState, ...props.value}),
[addToCart, getState, props.value]
);
return <MinicartContext.Provider value={value}>
{props.children}
</MinicartContext.Provider>
}