【问题标题】:Pass React.Context to Nextjs after ComponentDidMount?在 ComponentDidMount 之后将 React.Context 传递给 Nextjs?
【发布时间】:2020-08-27 01:59:18
【问题描述】:

我有一个问题,我有一个简单的 React.Context,它在所有组件安装之后 填充。问题是因为它发生在挂载之后,nextjs 在初始渲染时看不到这个数据,所以有明显的闪烁。

这是设置上下文的简单组件:

export const SetTableOfContents = (props: { item: TableOfContentsItem }) => {
  const toc = useContext(TableOfContentsContext);

  useEffect(() => {
    // Updates the React.Context after the component mount
    // (since useEffects run after mount)
    toc.setItem(props.item);
  }, [props.item, toc]);

  return null;
};

这里是 React.Context。它使用 React 状态来存储 TOC 项。

export const TableOfContentsProvider = (props: {
  children?: React.ReactNode;
}) => {
  const [items, setItems] = useState<TableOfContents["items"]>([]);

  const value = useMemo(() => {
    return {
      items,
      setItem(item: TableOfContentsItem) {
        setItems((items) => items.concat(item));
      },
    };
  }, [items]);

  return (
    <TableOfContentsContext.Provider value={value}>
      {props.children}
    </TableOfContentsContext.Provider>
  );
};

目前,无法设置 React.Context before 挂载,因为 React 会发出警告---无法在渲染时更新状态。

我能想到的唯一解决方法是使用 除了 React.state 之外的其他东西作为 React.Context 状态——这样组件就可以随时更新它。但是这种方法的问题是上下文消费者将不再知道项目发生了变化(因为更新存在于 React 生命周期之外)!

那么如何让初始的 React.Context 进入初始的 SSR 渲染呢?

const items = [];

export const TableOfContentsProvider = (props: {
  children?: React.ReactNode;
}) => {
  const value = useMemo(() => {
    return {
      items,
      setItem(item: TableOfContentsItem) {
        items[item.index] = item;
      },
    };
  // this dep never changes.
  // when you call this function, values never change
  }, [items]);

  return (
    <TableOfContentsContext.Provider value={value}>
      {props.children}
    </TableOfContentsContext.Provider>
  );
};

【问题讨论】:

  • getInitialProps做你想做的事的好地方
  • 你无权访问那里的上下文@EranGoldin
  • 对。那这个怎么样? stackoverflow.com/questions/54709299/…
  • 虽然不是很感谢@EranGoldin。发表了我最终做了什么

标签: next.js react-context


【解决方案1】:

这是我最终做的:

  • 使用renderToString 在getStaticProps 中渲染应用程序
  • 在上下文中使用useRef 代替useState
  • 这样做的原因是因为renderToString 只呈现初始状态。因此,如果您使用 useState 更新 Context,它将不会捕获后续渲染
  • 由于上述原因在组件初始化时更新上下文
  • 将“逃生舱口”传递给上下文——我们可以调用该函数来获取在初始渲染时计算的状态

是的,整个事情看起来就像一个巨大的黑客! :-) 我不确定 React.Context 是否与 SSR 配合得很好 :(

export const TableOfContentsProvider = (props: {
  initialItems?: TableOfContentsItem[];
  setItemsForSSR?: (items: TableOfContentsItem[]) => void;
  children?: React.ReactNode;
}) => {
  // use useRef for the reasons mentioned above
  const items = useRef(props.initialItems || []);
  // Client still needs to see updates, so that's what this is for
  const [count, setCount] = useState(0);

  const { setItemsForSSR } = props;

  const setterValue = useMemo(
    () => ({
      setItem(item: TableOfContentsItem) {
        if (!items.current.find((x) => x.id === item.id)) {
          items.current.push(item);
          items.current.sort((a, b) => a.index - b.index);
          setCount((count) => count + 1);
          setItemsForSSR?.(items.current);
        }
      },
    }),
    [setItemsForSSR]
  );

  const stateValue = useMemo(() => ({ items: items.current, count }), [count]);

  return (
    <TableOfContentsSetterContext.Provider value={setterValue}>
      <TableOfContentsStateContext.Provider value={stateValue}>
        {props.children}
      </TableOfContentsStateContext.Provider>
    </TableOfContentsSetterContext.Provider>
  );
};

interface TableOfContentsSetterWorkerProps {
  item: TableOfContentsItem;
  setItem: (item: TableOfContentsItem) => void;
}

export class TableOfContentsSetterWorker extends React.Component<
  TableOfContentsSetterWorkerProps,
  {}
> {
  constructor(props: TableOfContentsSetterWorkerProps) {
    super(props);
    // Need to do this on init otherwise renderToString won't record it
    props.setItem(props.item);
  }

  render() {
    return null;
  }
}

/**
 * Usage: use this as a child component when the parent needs to set the TOC.
 *
 * Exists so that a component can set the TOC without triggering
 * an unnecessary render on itself.
 */
export function TableOfContentsSetter(props: { item: TableOfContentsItem }) {
  const { setItem } = useContext(TableOfContentsSetterContext);

  return <TableOfContentsSetterWorker item={props.item} setItem={setItem} />;
export const getStaticProps = async () => {
  let initialTableOfContents: TableOfContentsItem[] = [];
  const getItems = (items: TableOfContentsItem[]) => {
    initialTableOfContents = [...items];
  };

  const app = () => (
    <TableOfContentsProvider setItemsForSSR={getItems}>
      <AppArticles />
    </TableOfContentsProvider>
  );

  renderToString(app());

  return {
    props: {
      initialTableOfContents,
    },
  };
};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-30
    • 2021-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多