Взаимодействие компонентов React / Gatsby (поднятие состояния) с компонентами в файлах MDX - PullRequest
1 голос
/ 03 августа 2020

Я использую Gatsby с плагином MDX. Так что я могу использовать компоненты React в уценке. Это нормально.

У меня есть компоненты, которые разговаривают друг с другом. Для этого я использую Шаблон подъема состояния подъема . Это нормально.

Вот базовый пример c счетчика, чтобы показать мой код подтверждения концепции.

import React from "react"

export class Counter extends React.Component {
  constructor(props) {
    super(props)
    this.state = { count: 0 }
    this.handleCounterUpdate = this.handleCounterUpdate.bind(this)
  }

  handleCounterUpdate() {
    this.setState({ count: this.state.count + 1 })
  }

  render() {
    const children = React.Children.map(this.props.children, child => {
      const additionalProps = {}
      additionalProps.count = this.state.count
      additionalProps.handleCounterUpdate = this.handleCounterUpdate
      return React.cloneElement(child, additionalProps)
    })
    return <div>{children}</div>
  }
}

export function Display({ count }) {
  return <h2>Current counter is: {count}</h2>
}

export function UpdateButton({ handleCounterUpdate }) {
  return <button onClick={handleCounterUpdate}>Increment couter by one</button>
}

При такой настройке можно использовать такие компоненты, как это

<Counter>
  <Display />
  <UpdateButton />
</Counter>

или даже так

<Counter>
  <Display />
  <UpdateButton />
  <Display />
  <Display />
</Counter>

Это нормально.

В реальном мире включающий компонент Counter (держатель состояния) будет чем-то вроде компонента Layout. <Layout> используется в шаблоне и отображает страницы многомерных выражений. Это выглядит так:

<SiteLayout>
  <SEO title={title} description={description} />
  <TopNavigation />
  <Display />       // The state holder is <SiteLayout>, not <Counter> 
  <Breadcrumb location={location} />
  <MDXRenderer>{page.body}</MDXRenderer>  // The rendered MDX
</SiteLayout>

<UpdateButton> (в реальном мире что-то вроде <AddToCartButton>) находится на странице MDX и больше не является прямым потомком компонента <Layout>.

Шаблон больше не работает.

Как я могу решить эту проблему?

Всем спасибо

1 Ответ

1 голос
/ 05 августа 2020
import React from "react"

// This is a proof of concept (POC) for intercomponent communication using
// React Context
//
// In the real world Gatsby/React app we use a kind of cart summary component
// at top right of each page. The site consists of thousands of pages with detailed
// product information and a blog. Users have the possibility to add products directly
// from product information pages and blog posts. Posts and pages are written in
// MDX (Mardown + React components). All <AddToCartButtons> reside in MDX files.
//
// This code uses a "increment counter button" (= add to cart button) and a
// display (= cart summary) as POC
//
// More information at
// https://reactjs.org/docs/context.html#updating-context-from-a-nested-component

export const CounterContext = React.createContext()

// The <Layout> component handles all business logic. Thats fine. We have
// very few app features.
export class Layout extends React.Component {
  constructor(props) {
    super(props)
    this.handleCounterUpdate = this.handleCounterUpdate.bind(this)
    this.state = { count: 0, increment: this.handleCounterUpdate }
  }

  handleCounterUpdate() {
    this.setState({ count: this.state.count + 1 })
  }
  render() {
    return (
      <div style={{ backgroundColor: "silver", padding: "20px" }}>
        <CounterContext.Provider value={this.state}>
          {this.props.children}
        </CounterContext.Provider>
      </div>
    )
  }
}

export class UpdateButton extends React.Component {
  static contextType = CounterContext
  render() {
    const count = this.context.count
    const increment = this.context.increment
    return (
      <button onClick={increment}>
        Increment counter (current value: {count})
      </button>
    )
  }
}

export class Display extends React.Component {
  static contextType = CounterContext

  render() {
    return (
      <div
        style={{
          backgroundColor: "white",
          padding: "10px",
          margin: "10px 0 0 0"
        }}
      >
        <div>I'm Display. I know the count: {this.context.count}</div>
        <div>{this.props.children}</div>
      </div>
    )
  }
}

// Function components use <CounterContext.Consumer>. Class components
// use static contextType = CounterContext
export function AChild() {
  return (
    <CounterContext.Consumer>
      {context => (
        <span>
          I'm a child of Display. I know the count too: {context.count}
        </span>
      )}
    </CounterContext.Consumer>
  )
}

Используйте это так

<Layout>
  <Display>
    <AChild />
  </Display>
  // UpdateButton inside MDX files
  <MDXRenderer>{page.body}</MDXRenderer>
 // Or elsewhere
  <UpdateButton /> 
</Layout>
...