同构样式加载器,无法读取null的属性“应用”

时间:2019-02-26 21:10:54

标签: javascript reactjs ssr isomorphic-style-loader

嗨,我已经看到了相同的错误和多种可能的解决方案,但是没有一个能够解决我的问题(可能是因为我对整个React结构缺乏深入的了解)。

我知道context.insertCss.apply(context,styles);没有收到上下文,这就是引发错误的原因,我添加了ContextProvider,但恐怕这可能与我的路由设置冲突。也使用了丹尼尔(Daniel)对这个问题的回答[Why does isomorphic-style-loader throw a TypeError: Cannot read property 'apply' of undefined when being used in unison with CSS-Modules

服务器index.js

    app.get('/*', (req, res) => {

  const matchingRoutes = matchRoutes(Routes, req.url);
  let promises = [];

  matchingRoutes.forEach(route => {
    if (route.loadData) {
      promises.push(route.loadData());
    }
  });

  // promise.then(data => {
  Promise.all(promises).then(dataArr => {
    // Let's add the data to the context
    // const context = { data };
    // const context = { dataArr };

    const css = new Set()
    const context = { insertCss: (...styles) => styles.forEach(style => css.add(style._getCss()))}

    const app = React.renderToString(
      <StaticRouter location={req.url}>
        <ContextProvider context={context}>
          <App/>  
        </ContextProvider>
      </StaticRouter>
    )

    const indexFile = path.resolve('./build/index.html');
    fs.readFile(indexFile, 'utf8', (err, indexData) => {
      if (err) {
        console.error('Something went wrong:', err);
        return res.status(500).send('Oops, better luck next time!');
      }

      if (context.status === 404) {
        res.status(404);
      }
      if (context.url) {
        return res.redirect(301, context.url);
      }

      return res.send(
        indexData
        .replace('<style id="myStyle"></style>',`<style type="text/css" id="myStyle">${[...css].join('')}</style>`)
          .replace('<div id="root"></div>', `<div id="root">${app}</div>`)
          .replace(
            '</body>',
            `<script>window.__ROUTE_DATA__ = ${serialize(dataArr)}</script></body>`
          )
      );
    });
  });
});

在服务器上的renderToString(..)方法中添加了ContextProvider,我也正在替换html正文,以便将接收到的CSS附加到HTML响应上。

ContextProvider.js

import React from 'react';
import PropTypes from 'prop-types'
import App from './App'

class ContextProvider extends React.Component {
  static childContextTypes = {
    insertCss: PropTypes.func,
  }

  getChildContext() {
    return {
      ...this.props.context
    }
  }

  render() {
    return <App {
      ...this.props
    }
    />
  }
}

export default ContextProvider

使用了丹尼尔(Daniel)的答案中的上下文提供程序(上文参考)

客户端index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import ContextProvider from './ContextProvider';


const context = {
  insertCss: (...styles) => {
    const removeCss = styles.map(x => x._insertCss());
    return () => {
      removeCss.forEach(f => f());
    };
  },
}


ReactDOM.hydrate(
  <BrowserRouter>
    <ContextProvider context={context} />
  </BrowserRouter>,
  document.getElementById('root')
);

按预期方式通过ContextProvider传递上下文。

在ContextProvider内部使用的App.js

import React from 'react';
import { renderRoutes } from 'react-router-config';
import { Switch, NavLink } from 'react-router-dom';

import Routes from './routes';

export default props => {
  return (
    <div>
      <ul>
        <li>
          <NavLink to="/">Home</NavLink>
        </li>
        <li>
          <NavLink to="/todos">Todos</NavLink>
        </li>
        <li>
          <NavLink to="/posts">Posts</NavLink>
        </li>
      </ul>

      <Switch>
        {renderRoutes(Routes)}
      </Switch>
    </div>
  );
};

我正在尝试测试自定义样式的Home.js

import React from 'react';
import withStyles from '../../node_modules/isomorphic-style-loader/withStyles'
import styles from '../scss/Home.scss';

function Home(props, context) {
  return (

      <h1>Hello, world!</h1>

  )
}

export default withStyles(styles)(Home);

routes.js描述了使用的路由。

import Home from './components/Home';
import Posts from './components/Posts';
import Todos from './components/Todos';
import NotFound from './components/NotFound';

import loadData from './helpers/loadData';

const Routes = [
  {
    path: '/',
    exact: true,
    component: Home
  },
  {
    path: '/posts',
    component: Posts,
    loadData: () => loadData('posts')
  },
  {
    path: '/todos',
    component: Todos,
    loadData: () => loadData('todos')
  },
  {
    component: NotFound
  }
];

export default Routes;

几乎可以确定此问题有一个简单的解决方法,但对我来说似乎并不那么琐碎。预先谢谢你。

1 个答案:

答案 0 :(得分:0)

请尝试使用isomorphic-style-loader的内置StyleContext代替自定义上下文提供程序。

server.js:

import StyleContext from 'isomorphic-style-loader/StyleContext';

const insertCss = (...styles) => {
  const removeCss = styles.map(style => style._insertCss());
  return () => removeCss.forEach(dispose => dispose());
};

ReactDOM.render(
  <StyleContext.Provider value={{ insertCss }}>
    <Router>{renderRoutes(Routes)}</Router>
  </StyleContext.Provider>,
  document.getElementById('root')
);

client.js:

app.get('/*', function(req, res) {

  const context = {};

  const css = new Set(); // CSS for all rendered React components
  const insertCss = (...styles) => styles.forEach(style => css.add(style._getCss()));

  const component = ReactDOMServer.renderToString(
    <StyleContext.Provider value={{ insertCss }}>
      <StaticRouter location={req.url} context={context}>
        {renderRoutes(Routes)}
      </StaticRouter>
    </StyleContext.Provider>
  );

  if (context.url) {
    res.writeHead(301, { Location: context.url });
    res.end();
  } else {
    res.send(Html('React SSR', component));
  }
});

您可以在此处查看示例项目:https://github.com/digz6666/webpack-loader-test/tree/ssr-2