Home组件仅在刷新时加载(而不在初始加载时加载),竞争条件?

时间:2019-01-29 23:50:56

标签: javascript html css reactjs

我遇到的情况是,第一次加载页面时,应在Home.js内部加载的图像没有加载,直到在浏览器(Chrome)上推送刷新为止。这是在开发服务器和实时生产服务器中发生的,有时必须多次刷新才能显示图像。

请注意,我对所有Web开发和React都是新手,因为我的大部分经验是在Android开发中。

我认为这与将图像加载到Home组件上的方式有关。通过日志记录,我发现图像是在页面加载之前(使用componentDidMount()之前加载的),因此我正在寻找可用的解决方案。

谢谢,这是代码...

Main.js:

import React from 'react';
import { withRouter, Route, NavLink, HashRouter } from 'react-router-dom';

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faInstagram, faGithub, faFacebook } from '@fortawesome/fontawesome-free-brands';
import 'bootstrap/dist/css/bootstrap.css';
import Favicon from 'react-favicon';

import Home from './Home';
import Contact from './Contact';
import socialMediaLinks from './utilities/Utils';

class Main extends React.Component {
    constructor() {
        super();
        this.state = { 
            screenWidth: null,
            isMobile: false,
        };
        this.handleResize = this.handleResize.bind(this);
    }

    componentDidMount() {
        window.addEventListener('resize', this.handleResize.bind(this));
        this.handleResize();
    }

    componentWillUnmount() {
        window.removeEventListener('resize', this.handleResize);
    }

    handleResize() {
        this.setState({ screenWidth: window.innerWidth });
        console.log(`width is ${this.state.screenWidth}`);
    }

    render() {        
        return (
            <HashRouter>
                <div>
                <Favicon url='./favicon.ico' />
                    {/* Navigation */}
                    <nav className='navbar fixed-top bg-dark flex-md-nowrap'>
                            {/* Social Media */}
                            <a className='social-media-link' href={socialMediaLinks['instagram']}><FontAwesomeIcon icon={faInstagram} size='lg' /></a>
                            <a className='social-media-link' href={socialMediaLinks['github']}><FontAwesomeIcon icon={faGithub} size='lg' /></a>
                            <a className='social-media-link' href={socialMediaLinks['facebook']}><FontAwesomeIcon icon={faFacebook} size='lg' /></a>
                            <ul className="header">
                                <li className='nav-option'><NavLink exact to='/'>Home</NavLink></li>
                                <li className='nav-option'><NavLink to='/contact'>About/Contact</NavLink></li>
                            </ul>
                    </nav>
                    {/* Main Page */}
                    <div className='content container-fluid' align='center'>
                        <div className='row'>
                            <Route exact path='/' component={withRouter(Home)} />
                            <Route path='/contact' component={withRouter(Contact)} />
                        </div>
                        <footer>Created by me :)</footer>
                    </div>
                </div>
            </HashRouter>
        );
    }
  }

export default Main;

Home.js:

import React from 'react';
import 'bootstrap/dist/css/bootstrap.css';
import './index.css';

var imageList = [];

function importAll(r) {
    const keys = r.keys();
    let images = {};

    for (var k in keys) {
        images[keys[k].replace('./', '')] = r(keys[k]);
    }

    return images;
}

const images = importAll(require.context('./images/resized/', false, /\.(png|jpe?g|svg)$/));

for (var image in images) {
    var newImage = new Image(images[image], images[image], null);
    newImage.name = images[image];
    newImage.src = images[image];
    imageList.push(newImage);
}

class Home extends React.Component {
    render() {
        let images = imageList.map(image => {
            if (image.naturalHeight > image.naturalWidth) {         // portrait
                return <img className='portrait-img' src={image.src} alt=''/>
            } else if (image.naturalHeight < image.naturalWidth) {  // landscape
                return <img className='landscape-img' src={image.src} alt=''/>
            }
        });

        return (
            <div>
                {images}
            </div>
        );
    }
}

export default Home;

index.css:

body {
    background-color: #FFF;
    padding: 20px;
    margin: 0;
    font-family: Helvetica;
}

h1 {
    color: #111;
    font-weight: bold;
    padding-top: 2px;
}

ul.header li {
    display: inline;
    list-style-type: none;
    margin: 0;
}

ul.header {
    background-color: #111;
    padding: 0;
}

ul.header li a {
    color: #FFF;
    font-weight: bold;
    text-decoration: none;
    padding: 20px;
    display: inline-block;
}

.content {
    background-color: #FFF;
    padding: 20px;
}

.content h2 {
    padding: 0;
    margin: 0;
}

.content li {
    margin-bottom: 10px;
}

.active {
      background-color: #0099FF;
}

.portrait-img {
    max-width: 55%;
    height: auto;
    padding: 5px;
}

.landscape-img {
    max-width: 75%;
    height: auto;
    padding: 5px;
}

.navbar {
    max-height: 110px;
    background-color: #FFCC00 !important;
}

.content {
    padding-top: 80px;
}

footer {
    background-color: #FFF;
    font-size: 7pt;
    position: relative;
    margin: auto;
    float: left;
}

.social-media-link {
    color: #111;
}

::-webkit-scrollbar {
    display: none;
}

@media only screen and (max-width: 500px) {
    h1 {
        font-size: 18pt;
    }

    .navbar {
        max-height: 120px;
    }

    .content {
        padding-top: 104px;
    }

    .header {
        font-size: 10pt;
    }

    .portrait-img {
        max-width: 100%;
        height: auto;
        padding: 2px;
    }

    .landscape-img {
        max-width: 100%;
        height: auto;
        padding: 2px;
    }

    footer {
        font-size: 5pt;
    }

    .social-media-link {
        padding: 5px;
    }
}

index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.1/css/font-awesome.min.css">
    <link rel='icon' href='../src/favicon.ico' type='image/x-icon' />
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

index.js:

import React from 'react';
import ReactDOM from 'react-dom';
import Main from './Main';
import './index.css';

ReactDOM.render(
    <Main />,
    document.getElementById('root'),
);

编辑:

下面修改了Home.js以使用Promise。这里的想法是,承诺渲染图像将更改this.state.images,并且该值将在HTML中使用,以返回组件的render()。这不能解决问题,但是会导致记录的imgs列表而不是以前的undefined列表。

import React from 'react';
import 'bootstrap/dist/css/bootstrap.css';
import './index.css';
import { resolve } from 'q';

var imageList = [];

function importAll(r) {
    const keys = r.keys();
    let images = {};

    for (var k in keys) {
        images[keys[k].replace('./', '')] = r(keys[k]);
    }

    return images;
}

const images = importAll(require.context('./images/resized/', false, /\.(png|jpe?g|svg)$/));

for (var image in images) {
    var newImage = new Image(images[image], images[image], null);
    newImage.name = images[image];
    newImage.src = images[image];
    imageList.push(newImage);
}

class Home extends React.Component {
    constructor(props) {
        super();
        this.state = {
            images: null,
        };
    }

    componentDidMount() {
        let promise = this.getImages();
        promise.then(result => {
            let images = result.map(image => {
                if (image.naturalHeight > image.naturalWidth) {         // portrait
                    return <img className='portrait-img' src={image.src} alt='' />
                } else if (image.naturalHeight < image.naturalWidth) {  // landscape
                    return <img className='landscape-img' src={image.src} alt='' />
                }
            });
            this.setState({ images: images });
        }, function(error) {
            this.setState({ images: error });
        });
    }

    getImages() {
        let promise = new Promise((resolve, reject) => {
            let imageList = [];
            const images = importAll(require.context('./images/resized/', false, /\.(png|jpe?g|svg)$/));
            for (var image in images) {
                var newImage = new Image(images[image], images[image], null);
                newImage.name = images[image];
                newImage.src = images[image];
                imageList.push(newImage);
            }

            if (imageList.length > 0) {
                console.log(imageList);
                resolve(imageList);
            } else {
                console.log('shit');
                reject(Error('oh shit'));
            }
        });

        return promise;
    }

    render() {
        return (
            <div>
                {this.state.images}
            </div>
        );
    }
}

export default Home;

结果是:

硬重载后(ctrl + F5): images = [undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined] 刷新后(ctrl + R): images = [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]-全部包含typeof: Symbol(react.element), type: "img"

2 个答案:

答案 0 :(得分:3)

console.log不能保证是同步的,因此您不能依赖它来执行。有些引擎是,有些不是。

我也不会依赖require.context,尤其是如果您使用的是create-react-app。参见:https://github.com/facebook/create-react-app/issues/517

通常,如果您的项目已经为ES模块设置,但是您发现自己需要require,则有点IMO的代码味道。 ES模块的全部重点是使作用域成为静态的,而不是动态的,以便编译器可以推断出缩小时要丢弃的内容。在运行时目录结构实际上不再存在。 Webpack开发服务器可以将所有文件最小化为一个大文件,也可以作为生产构建的一部分。

文件太多,无法直接导入模块吗?您可以为imageList有一个单独的模块来处理加载并导出它,然后只需import并直接在您的组件中使用:

// imageList.js

import foo from './images/foo.jpg'
import bar from './images/bar.jpg'
import baz from './images/baz.jpg'
import blah from './images/blah.jpg'

export default {
  foo,
  bar,
  baz,
  blah,
}

然后,您可以进行测试以确保将来不会遗漏任何信息(注意,测试直接在节点中以文件结构而不是由Webpack编写的最终程序运行):

// __tests__/imagesList.test.js

import imageList from '../imageList'
import fs from 'fs'
import path from 'path'

const dir = path.join(__dirname, '..', 'images')

it('has all the images', () => {
  const imageFiles = fs.readdirSync(dir).filter((file) => {
    return path.extname(file).match(/\.(png|jpe?g|svg)$/)
  })

  expect(Object.keys(imageList)).toEqual(imageFiles)
})

如果确实太多,或者它们经常更改,我通常希望在命令行上自动创建代码。以下是快速的n脏内容,但应根据路径接近您所需的内容:

#!/usr/bin/env node

const { readdirSync, writeFileSync } = require('fs')
const { extname, join } = require('path')

const relPath = (...paths) => join(__dirname, ...paths)
const dir = relPath('src', 'images')

const printLoaderFile = () => {
  const imageFiles = readdirSync(dir).filter((file) => {
    return extname(file).match(/\.(png|jpe?g|svg)$/)
  })

  const output = []

  imageFiles.forEach((file, index) => {
    output.push(`import image${index} from '${dir}/${file}'`)
  })

  output.push('\nexport default {')
  imageFiles.forEach((file, index) => {
    output.push(`  '${file}': image${index},`)
  })
  output.push('}\n')

  writeFileSync(relPath('src', 'imagesList.js'), output.join('\n'))
}

printLoaderFile()

答案 1 :(得分:0)

通过以下方式修改Home.js导致图像加载快速且没有刷新:

import React from 'react';
import 'bootstrap/dist/css/bootstrap.css';
import './index.css';

var imageList = [];

function importAll(r) {
    const keys = r.keys();
    let images = {};
    for (var k in keys) {
        images[keys[k].replace('./', '')] = r(keys[k]);
    }
    return images;
}

const images = importAll(require.context('./images/resized/', false, /\.(png|jpe?g|svg)$/));

for (var image in images) {
    var newImage = new Image(images[image], images[image], null);
    newImage.name = images[image];
    newImage.src = images[image];
    imageList.push(newImage);
}

class Home extends React.Component {
    constructor() {
        super();
        this.state = {
            images: [],
        }
    }

    componentDidMount() {
        this.setState({ images: imageList });
    }

    render() {
        let imagesFinal = [];
        for (var image in this.state.images) {
            console.log(this.state.images[image].src);
            if (this.state.images[image].naturalHeight > this.state.images[image].naturalWidth) {}
            imagesFinal.push(<img src={this.state.images[image].src} className='landscape-img' alt=''></img>);
        }

        return <div>{imagesFinal}</div>;
    }
}

export default Home;