在 ComponentDidMount 之后将 React.Context 传递给 Nextjs?

Posted

技术标签:

【中文标题】在 ComponentDidMount 之后将 React.Context 传递给 Nextjs?【英文标题】:Pass React.Context to Nextjs after ComponentDidMount? 【发布时间】: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 进入初始的 s-s-r 渲染呢?

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 对。那这个怎么样? ***.com/questions/54709299/… 虽然不是很感谢@EranGoldin。发表了我最终做了什么 【参考方案1】:

这是我最终做的:

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

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

export const TableOfContentsProvider = (props: 
  initialItems?: TableOfContentsItem[];
  setItemsFors-s-r?: (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  setItemsFors-s-r  = 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);
          setItemsFors-s-r?.(items.current);
        
      ,
    ),
    [setItemsFors-s-r]
  );

  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 setItemsFors-s-r=getItems>
      <AppArticles />
    </TableOfContentsProvider>
  );

  renderToString(app());

  return 
    props: 
      initialTableOfContents,
    ,
  ;
;

【讨论】:

以上是关于在 ComponentDidMount 之后将 React.Context 传递给 Nextjs?的主要内容,如果未能解决你的问题,请参考以下文章

componentWillMount和componentDidMount的区别

react请求接口数据是在componentDidMount 还是componentWillMount周期好

React componentDidMount 获取 api

goBack 导航调用后未调用 componentDidMount

如何让 componentDidMount 再次渲染?

我的道具在 React 的 ComponentDidMount 方法下的价值未定义