在 React 中,我如何检测我的组件是从客户端渲染还是从服务器渲染?

Posted

技术标签:

【中文标题】在 React 中,我如何检测我的组件是从客户端渲染还是从服务器渲染?【英文标题】:In React, how do I detect if my component is rendering from the client or the server? 【发布时间】:2015-11-19 21:19:21 【问题描述】:

我正在构建一个同构应用程序,但我正在使用仅在客户端上呈现的第三方组件。所以,特别是对于这个组件,我只需要在客户端渲染时渲染它。

如何检测我是在客户端还是在服务器端?我正在寻找类似isClient()isServer() 的东西。

【问题讨论】:

你不能检查一些像windowprocess这样的全局变量吗? 类似:***.com/a/13644360/251311 谢谢@elclanrs 和@zerkms。这是我想到的第一件事,但我正在尝试if(windows) ,而实际上我应该这样做typeof window ReactDOM.render( <Component />, document.getElementById('root') , () => console.log('render!)) 【参考方案1】:

在内部,React 为此使用了一个名为 ExecutionEnvironment 的实用程序。它实现了一些有用的属性,例如canUseDOMcanUseEventListeners。解决方案基本上就是here 所建议的。

canUseDOM的实现

var canUseDOM = !!(
  (typeof window !== 'undefined' &&
  window.document && window.document.createElement)
);

我在我的应用程序中这样使用它

var ExecutionEnvironment = require('react/node_modules/fbjs/lib/ExecutionEnvironment');
...
render() 
  <div> ExecutionEnvironment.canUseDOM ? this.renderMyComponent() : null </div>

编辑 这是一个不应该直接使用的未记录功能。它的位置可能会因版本而异。我通过展示 Facebook 团队内部使用的东西来分享这一点,以此表达“这是你能做的最好的”。您可能希望将此代码(它很小)复制到您自己的项目中,因此您不必担心在版本之间跟踪其位置或潜在的重大更改。

另一个编辑有人为此代码创建了npm package。我建议使用它。

npm install exenv --save

【讨论】:

只是好奇,为什么!!一开始? !! 将值转换为布尔值,以确保返回 truefalse 而不是可能的 undefined。您可以将上面的 !! 替换为 Boolean 并获得相同的结果 由于这里我们使用&amp;&amp; 运算符,我看不到最终返回未定义或除真或假以外的其他值的路径。我错了吗? 如果window.document 存在,但没有createElement 方法,这将返回undefined。如果它确实有一个createElement 方法,它将返回该方法又名一个函数。要对此进行测试,您可以将其复制并粘贴到您的 javascript 控制台中(不带 !!),然后查看它是否返回了一个函数【参考方案2】:

可能相关的两件事:

许多项目使用一些约定来设置全局 SERVER 或 CLIENT 布尔值,因此您的所有代码都可以基于它进行切换。在你的服务器包中,设置一些全局的,like in this project

global.__SERVER__ = true;

在您的客户端包中,将一些全局客户端设置为 true,您可以通过一种方式实现with Webpack's DefinePlugin

new webpack.DefinePlugin(
  __CLIENT__: true
)

通过上述方法,您可以在 willMount 或渲染中关闭该变量,以在服务器上做一件事,在客户端做另一件事。

这里可能有帮助的第二件事是componentDidMount 仅在客户端上运行,而不是在服务器上。

【讨论】:

能否请您查看我对此的回答?如有不妥之处,欢迎指正。【参考方案3】:

在服务器元素层次结构的最顶层,可以添加ServerContext,如下所示:

class ServerContext extends React.Component 
  getChildContext()  return  isServer: true ; 
  render()  return React.Children.only(this.props.children); 


ServerContext.propTypes = 
  children: React.PropTypes.node.isRequired,
;

ServerContext.childContextTypes = 
  isServer: React.PropTypes.bool.isRequired,
;

// Create our React application element.
const reactAppElement = (
  <ServerContext>
    <CodeSplitProvider context=codeSplitContext>
      <ServerRouter location=request.url context=reactRouterContext>
        <DemoApp />
      </ServerRouter>
    </CodeSplitProvider>
  </ServerContext>
);

这样做,应该可以像这样从上下文中读取 isServer:

const Layout = (_,  isServer ) => (
  // render stuff here
);

【讨论】:

【参考方案4】:

您也可以使用componentDidMount(),因为页面在服务器端呈现时不会运行此生命周期方法。

【讨论】:

【参考方案5】:

您可以在 exenv 包的帮助下创建一个有用的实用程序。

import  canUseDOM  from 'exenv';

export function onClient(fn: (..._args: any[]) => any): (..._args: any[]) => any 
    if (canUseDOM) 
        return fn;
    

    if (process.env.NODE_ENV === 'development') 
        console.log(`Called $fn.name on client side only`);
    

    return (): void => ;

并像这样使用它

function my_function_for_browser_only(arg1: number, arg2: string) 

onClient(my_function_for_browser_only)(123, "Hi !");

并且该函数只会在客户端调用,如果设置NODE_ENV=development,它会在服务器端登录该函数已在客户端调用

(这是打字稿,删除 JS 的类型 :))

【讨论】:

【参考方案6】:

您可以使用 reacts lifecyle 事件(例如:componentDidMount)来检测服务器/客户端渲染。

示例

作为钩子

import  useState, useEffect  from 'react'

function useIsServer () 
  const [isServer, setIsServer] = useState(true)
  useEffect(() => 
    setIsServer(false)
  , [])
  return isServer

用法

见下文(功能组件)

作为功能组件

import useIsServer from './above'

function ServerOnly ( children = null, onClient = null ) 
  const isServer = useIsServer()
  return isServer
    ? children
    : onClient

用法

<ServerOnly
  children='This String was rendered on the server'
  onClient='This String was rendered on the client'
/>

作为类组件

class ServerOnly extends React.Component 
  constructor (props) 
    super(props)
    this.state = 
      isServer: true
    
  

  componentDidMount() 
    this.setState(
      isServer: false
    )
  

  render () 
    const  isServer  = this.state
    const  children, onClient  = this.props
    return isServer
      ? children
      : onClient
  

用法

<ServerOnly
  children='This String was rendered on the server'
  onClient='This String was rendered on the client'
/>

【讨论】:

【参考方案7】:

你也可以只使用use-s-s-r React 钩子

import uses-s-r from 'use-s-s-r'

const App = () => 
  var  isBrowser, isServer  = uses-s-r()

  // Want array destructuring? You can do that too!
  var [isBrowser, isServer] = uses-s-r()

  /*
   * In your browser's chrome devtools console you should see
   * > IS BROWSER: ?
   * > IS SERVER: ?
   *
   * AND, in your terminal where your server is running you should see
   * > IS BROWSER: ?
   * > IS SERVER: ?
   */
  console.log('IS BROWSER: ', isBrowser ? '?' : '?')
  console.log('IS SERVER: ', isServer ? '?' : '?')
  return (
    <>
      Is in browser? isBrowser ? '?' : '?'
      <br />
      Is on server? isServer ? '?' : '?'
    </>
  )

Example

【讨论】:

数组解构对我不起作用。我使用过对象解构。【参考方案8】:

您可以检查是否定义了全局window 变量。 就像在浏览器中一样,它应该始终被定义。

var isBrowser = window!==undefined

【讨论】:

【参考方案9】:
if (typeof window === "undefined")  //client side code 

没有typeof,你会得到一个错误。

【讨论】:

以上是关于在 React 中,我如何检测我的组件是从客户端渲染还是从服务器渲染?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 react.js 的父组件中检测子渲染

道具React发生变化时如何渲染组件?

如何测试组件中的react useContext useReducer调度

如何在 React 中检测屏幕尺寸是不是已更改为移动设备?

ESLint 没有检测到导入的 React 组件

React:将状态传递给子组件