简体   繁体   English

渲染道具和反应路由器

[英]Render Props and React Router

I have an app where there are many pages that have similar outer functionality, but different inner functionality.我有一个应用程序,其中有许多页面具有相似的外部功能,但内部功能不同。 The outer functionality requires state, for example each page needs to have a date that can be changed on the page, without affecting the individual dates on the other pages.外部功能需要状态,例如每个页面都需要有一个可以在页面上更改的日期,而不影响其他页面上的各个日期。

I thought the best way to refactor this would be to use a decorator, either an HOC or a render prop.我认为重构它的最佳方法是使用装饰器,HOC 或渲染道具。 I went for render props but I'm very new to the pattern and I'm struggling to implement it.我去了渲染道具,但我对这种模式很陌生,我正在努力实现它。

Here's the layout - I've just used a simple counter to illustrate the problem I'm having creating separate decorated components with separate state.这是布局 - 我刚刚使用了一个简单的计数器来说明我在创建具有单独状态的单独装饰组件时遇到的问题。

I have an outer component which contains the React Router logic:我有一个包含 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>
  );
};

and the PageWrapper looks like this:和 PageWrapper 看起来像这样:

const PageWrapper = ({children}) => {

  const [counter, setCounter] = useState(0)

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

  return children({
      counter,
      incrementCounter
  });

};

export default PageWrapper;

Finally, each of the sections looks like this:最后,每个部分如下所示:

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

So the properties object that I'm passing to props.children contains the counter and the incrementCounter method.所以我传递给 props.children 的属性对象包含计数器和 incrementCounter 方法。

My issue is that when I go to Section1Test with the route, and increment the counter, it's also incrementing the counter of Section2Test.我的问题是,当我使用路由转到 Section1Test 并增加计数器时,它也会增加 Section2Test 的计数器。 This defeats the purpose of having a decorator, since the components are clearly sharing state.这违背了拥有装饰器的目的,因为组件显然共享状态。

It would be great if someone could help me see where the problem is.如果有人能帮我看看问题出在哪里,那就太好了。

Many thanks.非常感谢。

Well, you are sharing the state between all the components that are inside PageWrapper.好吧,您正在 PageWrapper 内部的所有组件之间共享状态。 One way to decouple it would be to make a custom hook, as an alternative to HOC:解耦它的一种方法是制作一个自定义钩子,作为 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>
);

So, I actually did some work on this just for fun, feeling like I need to get better at various things in React.所以,我实际上做了一些工作只是为了好玩,感觉我需要在 React 的各种事情上变得更好。 I did some reading on Higher Order Components and I actually developed a Higher Order Component that you feed your existing component to, it handles the state and sends down {counter, incrementCounter} as props, it also maintains state based on the name of the component you feed it (you could modify it to maintain state based on an optional variable or prop as well, if you wanted to)我在高阶组件上做了一些阅读,实际上我开发了一个高阶组件,您可以将现有组件提供给它,它处理状态并发送 {counter, incrementCounter} 作为道具,它还根据组件的名称维护状态你喂它(如果你想,你也可以修改它以维护基于可选变量或道具的状态)

Check it out:一探究竟:

HOC.js : 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 : 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>
        )
    }
}

Make a section2 that's mostly similar but a little different, and then do this in your component that's supposed to house section1 and section2:创建一个大部分相似但略有不同的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>

Now section1 and section2 will have unique counters, and when you go back to section1 or section2, the counter state will be the same as it was when you left it!现在 section1 和 section2 将拥有唯一的计数器,当您返回 section1 或 section2 时,计数器状态将与您离开时相同!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM