ReactSSR:期望的服务器HTML在

时间:2019-03-05 08:31:45

标签: reactjs

很抱歉打扰您以检查我的问题,我已经在stackoverflow中自动搜索了我的问题,并在之前阅读了此内容:same question as mine

它提到了解决此警告的这种方式,但我认为这不是解决该问题的真正好方法: suppressHydrationWarning = {true} prop也可以在渲染的元素上使用。但是,从文档的角度看,该道具必须很少使用。更好的解决方案是适当地使用hydrate()或render()

所以,这是我使用React SSR时遇到的麻烦:

  1. 我启动nodejs服务器,然后在浏览器中请求路由。
  2. 服务器收到我的请求后,应将其返回给服务器渲染 模板到浏览器。
  3. 我可以看到元素在第一个屏幕中呈现,这意味着dom已成功安装在。
  4. 当我单击将触发配置中的路由的元素时,也可以呈现。现在,一切都很好。
  5. 这里有件事:当我在浏览器中刷新页面时,如果路由中的路由调用“ / text1”或“ / text2”,也会出现类似我的问题标题的警告:预期的服务器HTML包含。匹配项。
  6. 我怀疑在nodejs中的路由和客户端中的某些特定代码之间是否处理不好,这引起了我的疑问。

这是我的特殊代码[fake]:

// app.js

const http = require('http')
const fs = require('fs')
const path = require('path')

const demo = require('./demo')
const clientScripts = demo('Client')
let scriptsTag = ''
clientScripts.map((script) => {
	scriptsTag += `<script src=${script}></script>`
})

const server = http.createServer((req, res) => {
	res.setHeader("Access-Control-Allow-Origin","*");
	res.setHeader("Access-Control-Allow-Headers","*");
	
    // ssr
    const ssrObj = require('./static/entry/serverEntry')
    const dom = ssrObj.inital('server').dom
    const store = ssrObj.inital('server').store
    // const title = ssrObj.inital('server').title
    console.log('in: ', dom)
    res.setHeader("Content-Type","text/html;charset=utf-8");
    res.end(`
        <html>
            <head>
                <title>React & React Router4 SSR</title>
            </head>
            <body>
                <!-- server side -->
                <div id="root">${dom}</div>
                <script>window.__PRELOADED_STATE__ = ${JSON.stringify(store)}</script>
                <!-- ok with client side -->
                ${scriptsTag}
            </body>
        </html>
    `);
});
server.listen(1234, () => {
	console.log('开始监听1234端口')
})

// demo.js

const path = require('path')
const fs = require('fs')
let targetFile = ''

// suppose webpack configuration are ok, its' server output set in '/dist'
const fileList = fs.readdirSync(path.resolve(__dirname, '../dist'))
const container = []

module.exports = (params) => {
    fileList.map((file) => {
        const ext = path.extname(file).slice(1)
        if (ext === 'js') {
            const reg = new RegExp(`${params}`,"gim");
            if (reg.test(file)) {
                container.push(file)
            }
        }
    })
    return container
}

// /entry/serverEntry.js

require('babel-polyfill')
require('babel-register')({
    presets: [ 'env' ]
})
const App = require('../common/initalEntry')
module.exports = App

// /entry/client.js

require('babel-polyfill')
require('babel-register')({
    presets: [ 'env' ]
})
const App = require('../common/initalEntry')
App.inital('client').dom

// /common/initalEntry.js

import React from 'react';
// dom
import {hydrate} from 'react-dom'   // client side use hydrate to replace render in react16
import {renderToString} from 'react-dom/server'
// router
import {StaticRouter, BrowserRouter} from 'react-router-dom'
// store
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import * as reducers from '../store/reducers'
import { App } from './App'

export function inital (url = '') {
    if (url === 'server') {
        console.log(1, url)
        const serverStore = createStore(reducers.counter)
        return {
            dom: renderToString(
                <Provider store={serverStore}>
                    <StaticRouter location={url} context={{}}>
                        <App type={url} />
                    </StaticRouter>
                </Provider>
            ),
            store: serverStore
        }
    } else if (url === 'client') {
        console.log(2, url, App)
        const clientStore = createStore(reducers.counter, window.__PRELOADED_STATE__)
        delete window.__PRELOADED_STATE__

        return {
            dom: hydrate(
                <Provider store={clientStore}>
                    <BrowserRouter>
                        <App type={url} />
                    </BrowserRouter>
                </Provider>
                , document.getElementById('root')
            ),
            store: clientStore
        }
    }
}
// common / App.js

import React from 'react';
import {Route, Link} from 'react-router-dom'

class Text1 extends React.Component {
    constructor (props) {
        super(props)
    }
    render () {
        return (
            <div>i am text1.</div>
        )
    }
}

class Text2 extends React.Component {
    render () {
        return (
            <div>i am text2.</div>
        )
    }
}

export class App extends React.Component {
    constructor (props) {
        super(props)
    }
    componentDidMount () {
        console.log(this.props, '<<<<<<<')
    }
    goTo () {
    }
    render () {
        return (
            <div>
                <Link to="/text1">go text1</Link>
                <Link to="/text2">go text2</Link>
                <Route path="/text1" component={Text1}></Route>
                <Route path="/text2" component={Text2}></Route>
            </div>
        )
    }
}

首先,这些是我对导致此问题的react ssr的配置。感谢查看我的问题,请给我一些处理此问题的想法。我将非常感谢您的帮助。

这是我的完整代码:just viewing and running the server/ directory is ok

再次感谢您的帮助。


我删除了App.js中的代码,然后不再显示警告,这是我的修改:

import React from 'react';
import {Route, Link} from 'react-router-dom'

export class App extends React.Component {
    constructor (props) {
        super(props)
    }
    goTo () {
        console.log('click me')
    }
    render () {
        return (
            <div>
                <p onClick={this.goTo.bind(this)}>123</p>
            </div>
        )
    }
}

1 个答案:

答案 0 :(得分:0)

// I finally solve this problem when I find something a litte bit strange in /common/initalEntry.js
import React from 'react';
// dom
import {hydrate, render} from 'react-dom'
import {renderToString} from 'react-dom/server'
// router
import {StaticRouter, BrowserRouter} from 'react-router-dom'
// store
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import * as reducers from '../store/reducers'
import { App } from './App'

export function inital (type, url = '') {
    if (type === 'server') {
        const serverStore = createStore(reducers.counter)
        return {
            dom: renderToString(
                <Provider store={serverStore}>
                    {/* What I pass in location is a empty value which calls 'url' which causes me a lot of time to figure out what happened.The right way is pass the request url received by node.js server to 'location', then no more warning */}
                    <StaticRouter location={url} context={{}}>
                        <App />
                    </StaticRouter>
                </Provider>
            ),
            store: serverStore
        }
    } else if (type === 'client') {
        // ...ignore
    }
}