javascript - 如何在 App shell 和 SSR 中实现兄弟组件通信

标签 javascript reactjs

我采用了一个基于 this starter kit 的项目.该架构采用 App Shell 和 SSR。我正在尝试添加一个简单的搜索栏,这意味着将搜索键从搜索栏组件传递到后列表组件,以便可以过滤它们。我发现这对于上下文提供者和消费者来说几乎是不可能的。我想使用 Context,但我不知道该怎么做。看起来这个入门套件有一个严重的缺点,如果可以解决,它将使这个套件在网上更有用。
如果您查看下面的代码和上面的链接,您会看到有一个标题中心,然后就是页面。我需要标题和页面之间的通信。您可以只使用链接中的代码来添加兄弟通信。
Hydrate 的使用似乎排除了上下文提供者的简单应用。 Hydrate 以并行方式添加组件,而无法将 Context Provider 放在它们之上。我在这里使用的这种模式不起作用。当我更新提供者时,它不会导致重新呈现上下文消费者。
如果我必须使用 Context 以外的东西,比如 Redux,那么我会接受这个答案。
这是客户端入口点:

import { onPageLoad } from 'meteor/server-render';
import MeteorLoadable from 'meteor/nemms:meteor-react-loadable';
import { Switch, Route, Router, Redirect } from 'react-router-dom';
import { ApolloClient } from 'apollo-client';
import { ApolloProvider } from 'react-apollo';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloLink } from 'apollo-link';
import { HttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { onError } from 'apollo-link-error';
import apolloLogger from 'apollo-link-logger';
import { onTokenChange, getLoginToken } from '/app/ui/apollo-client/auth';
import createBrowserHistory from 'history/createBrowserHistory';
import 'unfetch/polyfill';

// Initialise react-intl
import { primaryLocale, otherLocales } from '/app/intl';

// Need to preload of list of loadable components for MeteorLoadable
import '/app/ui/loadables';

// To get started, create an ApolloClient instance and point it at your GraphQL
// server. By default, this client will send queries to the '/graphql' endpoint
// on the same host.

// To avoid asynchronously accessing local storage for every GraphQL request,
// we cache the authorisation token, and update it using an onTokenChange callback
let authToken;
let authTokenInitialised = false;
onTokenChange(({ token }) => { authToken = token; authTokenInitialised = true; });

const withAuthToken = setContext(() => {
  if (authTokenInitialised) {
    return authToken ? { headers: { authorization: authToken } } : undefined;
  }

  return getLoginToken()
    .then((token) => {
      authToken = token;
      authTokenInitialised = true;
      return authToken ? { headers: { authorization: authToken } } : undefined;
    });
});

const resetAuthToken = onError(({ networkError }) => {
  if (networkError && networkError.statusCode === 401) {
    // Remove cached token on 401 from the server
    authToken = null;
    authTokenInitialised = false;
  }
});

const onErrorLink = onError(({ graphQLErrors, networkError }) => {
  if (graphQLErrors) {
    graphQLErrors.map(({ message, locations, path }) => console.log(
      `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
    ));
  }
  if (networkError) console.log(`[Network error]: ${networkError}`);
});

const client = new ApolloClient({
  link: ApolloLink.from([
    apolloLogger,
    withAuthToken,
    resetAuthToken,
    onErrorLink,
    new HttpLink({
      uri: '/graphql',
    }),
  ]),
  cache: new InMemoryCache().restore(window.__APOLLO_STATE__),
});

// Inject the data into the app shell.
// If the structure's changed, ssr.js also needs updating.
async function renderAsync() {
  const [
    React,
    { hydrate, render },
    { default: App },
    { default: HeaderTitle },
    { default: LanguagePicker },
    { default: Routes },
    { default: Menu },
  ] = await Promise.all([
    import('react'),
    import('react-dom'),
    import('/app/ui/components/smart/app'),
    import('/app/ui/components/smart/header/header-title'),
    import('/app/ui/components/dumb/language-picker'),
    import('/app/ui/routes'),
    import('/app/ui/components/smart/menu'),
    MeteorLoadable.preloadComponents(),
  ]);

  // Given that we are implementing App Shell Architecture and, therefore,
  // injecting (via reactDOM.render) the Header, Menu and Main components into
  // different HTML elements, we need a way to share the router 'history' among
  // all three mentioned components.
  // As a default, for every invocation of 'BrowserRouter', there will be new
  // 'history' instance created. Then, changes in the 'history' object in one
  // component won't be available in the other components. To prevent this, we are
  // relying on the 'Router' component instead of 'BrowserRouter' and defining our
  // custom 'history' object by means of 'createBrowserHistory' function. Said
  // 'history' object is then passed to every invocation of 'Router' and therefore
  // the same 'history' object will be shared among all three mentioned components.

  const history = createBrowserHistory();

  // Inject react app components into App's Shell
  const ClientApp = ({ component }) => (
    <Router history={history}>
      <ApolloProvider client={client}>
        <Switch>
          {/* Map our locales to separate routes */}
          { otherLocales.map(locale => (
            <Route
              key={locale}
              path={`/${locale}/`}
              render={props => <App component={component} {...props} locale={locale} section="app" />}
            />
          ))}

          { primaryLocale && (
            <Route
              key={primaryLocale}
              path="/"
              render={props => <App component={component} {...props} locale={primaryLocale} section="app" />}
            />
          )}

          {/* If no valid locale is given, we redirect to same route with the preferred locale prefixed */}
          <Route render={({ location }) => <Redirect to={`/${window.__PREFERRED_LOCALE__ || otherLocales[0]}${location.pathname}`} />} />
        </Switch>
      </ApolloProvider>
    </Router>
  );

  render(<ClientApp component={Menu} />, document.getElementById('menu'));

  hydrate(<ClientApp component={HeaderTitle} />, document.getElementById('header-title'));
  hydrate(<ClientApp component={LanguagePicker} />, document.getElementById('header-lang-picker'));

  hydrate(<ClientApp component={Routes} />, document.getElementById('main'));
}

onPageLoad(() => {
  const renderStart = Date.now();
  const startupTime = renderStart - window.performance.timing.responseStart;
  console.log(`Meteor.startup took: ${startupTime}ms`);

  // Register service worker
  import('/app/ui/register-sw').then(() => {});

  renderAsync().then(() => {
    const renderTime = Date.now() - renderStart;
    console.log(`renderAsync took: ${renderTime}ms`);
    console.log(`Total time: ${startupTime + renderTime}ms`);
  });
});

最佳答案

你可以创建一个上下文,比如说 AppContext

// you can create some help such as the AppProvider and the useAppContext hook
import { createContext, useContext, useState } from "react";

export const AppContext = createContext({});

export const AppProvider: any = ({ initialState = { search: "" }, children }) => {

    const [search, setSearch] = useState(initialState);
    return (
        <AppContext.Provider value={[search, setSearch]}>
            {children}
        </AppContext.Provider>
    )
};

export const useAppContext: any = () => useContext(AppContext);
那么您可以将您的提供商插入到 apolloProvider 的同一级别
...

import {AppProvider} from './context/AppProvider';
....

<AppProvider>  // optionally you can pass an initialState to you context as prop
   <ApolloProvider client={client}>
      // your code         
   </ApolloProvider>
</AppProvider>
...
最后,您可以使用 search 中定义的状态 ( AppContext )

import {useAppContext} from './context/AppProvider';
....

export const Component = () => {

   const [search, setSearch] = useAppContext()

   return <div>{search}</div>
}


关于javascript - 如何在 App shell 和 SSR 中实现兄弟组件通信,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63911883/

相关文章:

javascript - 如何将 for 循环中的每个项目添加到 ReactJS 数组中

javascript - 如何在组件外调度 redux Action ?

javascript - 使用 ES6 文件进行 Stryker 突变测试

javascript - 将动态 bool 属性传递给 VueJS 组件

javascript - 如何在具体情况下停止隐藏下拉内容?

javascript - 如何使用具有多个值的endsWith?

reactjs - Flow React : Cannot create element because React. 组件 [1] 不是 React 组件

reactjs - 无法让 @mui 5 样式的组件工作

javascript - JavaScript 错误捕获的正确粒度是多少?

javascript - 构建空闲游戏对象的最佳方法