以下内容适用于通过NextJS的SSR。
我使用React的上下文来跟踪某些已安装组件的ID。要点是
class Root extends React.Component {
getChildContext () {
return {
registerComponent: this.registerComponent
}
}
registerComponent = (id) => {
this.setState(({ mountedComponents }) => {
return { mountedComponents: [...mountedComponents, id ] }
})
}
...
}
class ChildComponent {
static contextTypes = { registerComponent: PropTypes.func }
constructor(props) {
super(props)
props.registerComponent(props.id)
}
}
不幸的是,这只适用于客户端。服务器上的this.state.mountedComponents
始终为[]
。是否有另一种方法来跟踪这些组件服务器端?基本上我需要使用id来提供脚本以在文档的head
中运行 - 等待客户端应用程序安装,运行并手动附加到头部有点太慢。
更新
这是一个快速示例回购:https://github.com/tills13/nextjs-ssr-context
this.context
在undefined
的构造函数中为Child
,如果我将其移至componentDidMount
(目前在回购中以这种方式设置),则可行,但我和#39; d喜欢这个在服务器端解析。我context
并没有死定,如果还有其他办法,我全心全意。
答案 0 :(得分:3)
我已经对此进行了深入研究,我的感觉是由于多种原因这是不可能的。
setState
更新是异步的,在您的情况下将不会执行。
provider
的渲染甚至会在状态更新之前发生,因此注册代码将在以后执行,并且渲染功能没有最新状态
我放入了不同的console.log
,下面是我得到的
Provider constructed with { mountedComponents: [],
name: 'name-Sun Jun 24 2018 19:19:08 GMT+0530 (IST)' }
getDerivedStateFromProps
Renderring Provider Component { mountedComponents: [],
name: 'name-Sun Jun 24 2018 19:19:08 GMT+0530 (IST)' }
data loaded
constructor Child { id: '1' } { registerComponent: [Function: value] }
Register Component called 1 { mountedComponents: [],
name: 'name-Sun Jun 24 2018 19:19:08 GMT+0530 (IST)' }
Registering component 1
constructor Child { id: '2' } { registerComponent: [Function: value] }
Register Component called 2 { mountedComponents: [ '1' ],
name: 'tarun-Sun Jun 24 2018 19:19:08 GMT+0530 (IST)' }
Registering component 2
constructor Child { id: '3' } { registerComponent: [Function: value] }
Register Component called 3 { mountedComponents: [ '1', '2' ],
name: 'name-Sun Jun 24 2018 19:19:08 GMT+0530 (IST)' }
Registering component 3
constructor Child { id: '4' } { registerComponent: [Function: value] }
Register Component called 4 { mountedComponents: [ '1', '2', '3' ],
name: 'name-Sun Jun 24 2018 19:19:08 GMT+0530 (IST)' }
Registering component 4
该信息实际上仅在发生componentDidMount
时可用。但是由于在服务器端渲染不会发生的情况下,您不会获得数据。
我正在进一步研究是否有可用的黑客手段,但是到目前为止,我不确定
答案 1 :(得分:2)
可以在构造函数内使用 context API,您只需将其作为参数传递给构造函数和 super 方法如下:
export class Child extends React.Component {
static contextTypes = { registerComponent: PropTypes.func };
constructor(props, context) {
super(props, context);
context.registerComponent(props.id);
}
render() {
return <div id={this.props.id} />;
}
}
更新:
因此,问题与服务器上的第一个渲染不支持任何交互性有关,
这种方法将阻止在服务器上所有setState
的情况下使用,例如您的<Provider />
不会给予任何帮助,因此我们需要一种变通办法,无论如何都将是一个hack。
我得到的解决方案是每个跟踪的组件渲染都使用一个新的window.ids
(使用nextjs提供的Head):
export class Child extends React.Component {
registerComponent = (id) => `
if (window.ids) {
window.ids = [...window.ids, ${id}];
} else window.ids = [${id}];
`;
render() {
return (
<Fragment>
<Head>
<script
className="track"
dangerouslySetInnerHTML={{
__html: `${this.registerComponent(this.props.id)}`,
}}
/>
</Head>
<div id={this.props.id} />
</Fragment>
);
}
}
因此window.ids
变量将在<App /> renders
之前可用。
这是repos link进行测试。
另一种解决方案可能是在服务器上使用全局变量,然后我们可以在每个跟踪的组件中进行突变
使用 componentWillMount 生命周期挂钩的全局变量,因为它将仅在服务器上执行,然后
将这些ID注入html模板<head />
中,但是如果我们正在执行renderToString方法,则可以这样做。
第二种解决方案:使用页面/ _document 而不是页面/ _app ,这样我们就可以访问服务器before it renders to string!
:
这是仓库分支:origin/workaround-using-document
-子组件:
export class Child extends React.Component {
render() {
return (
<Fragment>
<Head>
<meta className="track" id={this.props.id} />
</Head>
<div id={this.props.id} />
</Fragment>
);
}
}
-文档组件(取代应用程序):
export default class MyDocument extends Document {
render() {
const { head } = this.props;
const idsFromHead = head.reduce(
(acc, { props }) =>
(props.className.includes('track') && [...acc, props.id]) || acc,
[]
);
return (
<html>
<Head>
<script
dangerouslySetInnerHTML={{
__html: `window.ids=[${idsFromHead}]`,
}}
/>
</Head>
<body className="custom_class">
<h3>{idsFromHead}</h3>
<Main />
<NextScript />
</body>
</html>
);
}
}