渲染道具和反应路由器

Render Props and React Router

我有一个应用程序,其中有许多具有相似外部功能但内部功能不同的页面。外部功能需要状态,例如每个页面都需要有一个可以在页面上更改的日期,而不会影响其他页面上的各个日期。

我认为重构它的最佳方法是使用装饰器,HOC 或渲染道具。我去了渲染道具,但我对这种模式很陌生,我正在努力实施它。

这是布局 - 我刚刚使用了一个简单的计数器来说明我在创建具有单独状态的单独装饰组件时遇到的问题。

我有一个包含 React Router 逻辑的外部组件:

  return (
    <PageLayout>
      <Grid item xs={12}>
        <Grid container spacing={2}>

          <Switch>
            <Route
              exact
              path="/section1/overview"
              render={() => (
                  <>
                  {<PageWrapper>{pageProperties => <Section1Test properties={pageProperties}/>}</PageWrapper>}
                  </>
              )}
            />
            <Route
              exact
              path="/section2/overview"
              render={() => (
                  <>
                  {<PageWrapper>{pageProperties => <Section2Test properties={pageProperties}/>}</PageWrapper>}
                  </>
              )}
            />
          </Switch>
        </Grid>
      </Grid>
    </PageLayout>
  );
};

PageWrapper 看起来像这样:

const PageWrapper = ({children}) => {

  const [counter, setCounter] = useState(0)

  const incrementCounter = () => {
    setCounter(counter + 1)
  }

  return children({
      counter,
      incrementCounter
  });

};

export default PageWrapper;

最后,每个部分看起来像这样:

const { counter, incrementCounter } = properties;
    return (
        <div>
            {counter}
            <button onClick={incrementCounter}>Click me</button>
        </div>

所以我传递给 props.children 的属性对象包含计数器和 incrementCounter 方法。

我的问题是,当我使用路线转到 Section1Test 并增加计数器时,它也会增加 Section2Test 的计数器。这违背了装饰器的目的,因为组件显然共享状态。

如果有人能帮我看看问题出在哪里,那就太好了。

非常感谢。

好吧,您正在 PageWrapper 内的所有组件之间共享状态。解耦它的一种方法是制作一个自定义钩子,作为 HOC 的替代方法:

const useCounter = () => {
  const [counter, setCounter] = useState(0)

  const incrementCounter = () => {
    setCounter(counter + 1)
  }

  return {counter, incrementCounter};
}

// Inside component

const { counter, incrementCounter } = useCounter();
return (
  <div>
    {counter}
    <button onClick={incrementCounter}>Click me</button>
  </div>
);

所以,实际上我做了一些这方面的工作只是为了好玩,感觉我需要在 React 的各个方面做得更好。我读了一些高阶组件,实际上我开发了一个高阶组件,你将现有组件提供给它,它处理状态并将 {counter, incrementCounter} 作为道具发送,它还根据组件的名称维护状态你喂它(如果你愿意,你也可以修改它以根据可选变量或道具维持状态)

查看:

HOC.js:

import React from 'react';
import autoBind from 'react-autobind';

export default function HOC(WrappedComponent)  {
    const componentName = WrappedComponent.name;

    return class extends React.Component {

        constructor(props) {
            super(props);

            this.state = getState(componentName);

            autoBind(this);
        }

        addOne() {
            this.setState({counter: this.state.counter+1}, () => {
                updateState(componentName, this.state);
            })
        }

        render() {
            return (
                <div>
                    <p>this component is wrapped</p>
                    <WrappedComponent properties={{counter: this.state.counter, incrementCounter: this.addOne}}/>
                </div>
            )
        }
    };
}

const states = {};
function getState(name) {
    if (name in states) return states[name];
    return {counter: 0}
}

function updateState(name, newState) {
    states[name] = newState
}

section1test.js:

import React from 'react';

export default class Section1Test extends React.Component {

    constructor(props) {
        super(props);
    }

    render() {
        const { counter, incrementCounter } = this.props.properties;
        return (
            <div>
                <p>section1</p>
                <input value={counter && counter.toFixed(2)}/> <br/>
                <button onClick={incrementCounter}>Click me</button>
            </div>
        )
    }
}

制作一个 section2 大部分相似但有一点不同的东西,然后在应该包含 section1 和 section2 的组件中执行此操作:

// have this be ABOVE the class definition
const WrappedSection1 = HOC(Section1Test);
const WrappedSection2 = HOC(Section2Test);

...
// and then inside the render function
               <Link to="/section3/overview">Three</Link><br/>
                <Link to="/section4/overview">Four</Link><br/>
                <Switch>
                    <Route
                        exact
                        path="/section3/overview"
                        render={() => (
                            <WrappedSection1 />
                        )}
                    />
                    <Route
                        exact
                        path="/section4/overview"
                        render={() => (
                            <WrappedSection2 />
                        )}
                    />
               </Switch>

现在 section1 和 section2 将具有唯一的计数器,当您返回 section1 或 section2 时,计数器状态将与您离开时的状态相同!