慕课网 Gallary-by-React 学习笔记

  LeungJZ

项目地址

该项目是来自慕课网的实战项目 - React实践图片画廊应用(下),原实战教程中,所用到的脚手架为 yeoman 中的 generator-react-webpack 进行搭建,但是该脚手架已经快一年没有更新,而且其 2.0 版本也与食品教程中的项目结构都不一致,因此在该项目中使用了 React 官方团队维护的 creat-react-app 作为脚手架搭建项目。

项目中使用 es6 的语法,并且对各个组件和功能模块进行了拆分,实现可复用,高定制的效果。

技术栈

  • ES6
  • React(version: 16.1)
  • webpack(version: 3)
  • creat-react-app
  • sass / scss suport

踩坑

  1. 安装脚手架
$ npm install -g create-react-app
  1. 使用脚手架新建项目
$ create-react-app my-project
$ cd my-project
$ npm start
  1. 处理 scss/sass 文件

#### 处理方法一(脚手架官方推荐):

先安装 node-sass-chokidaryarn add node-sass-chokidar

package.json 添加新的命令:

{
"scripts": {
    "build-css": "node-sass-chokidar src/ -o src/",
    "watch-css": "npm run build-css && node-sass-chokidar src/ -o src/ --watch --recursive",
    "start-js": "react-scripts start",
    "start": "npm-run-all -p watch-css start-js",
    "build-js": "react-scripts build",
    "build": "npm-run-all build-css build-js",
}
}

至于为什么使用该插件而不直接使用 node-sass 配合 sass-loader,官方给出了这样的答案:

> Why node-sass-chokidar?
> node-sass has been reported as having the following issues:
>
> - node-sass --watch has been reported to have performance issues in certain conditions when used in a virtual machine or with docker.
> - Infinite styles compiling #1939
> - node-sass has been reported as having issues with detecting new files in a directory #1891
> node-sass-chokidar is used here as it addresses these issues.

#### 处理方法二:

脚手架原生的项目仅支持 css 文件。如果需要 webpack 处理 scss/sass 的文件的话,需要手动修改 webpack 的配置,该配置文件为: ./node_modules/react-scripts/config/webpack.config.dev.js./node_modules/react-scripts/config/webpack.config.dev.js。在该俩文件中的 添加下列代码:

module.exports = {
  ...
  module: {
  rules: [{
    test: /\.scss$/,
    use: [
          require.resolve('style-loader'),
          {
              loader: require.resolve('css-loader'),
              options: {
                  importLoaders: 1,
              },
          },
          {
              loader: require.resolve('postcss-loader'),
              options: {
                  // Necessary for external CSS imports to work
                  // https://github.com/facebookincubator/create-react-app/issues/2677
                  ident: 'postcss',
                  plugins: () => [
                      require('postcss-flexbugs-fixes'),
                      autoprefixer({
                          browsers: [
                              '>1%',
                              'last 4 versions',
                              'Firefox ESR',
                              'not ie < 9', // React doesn't support IE8 anyway
                          ],
                          flexbox: 'no-2009',
                      }),
                  ],
              },
          },
          {
              loader: require.resolve('sass-loader')
          }
      ]
  }]
}
}	
  1. 分析图片放置位置

如下图所示:

绿色为居中图片的位置。

红色框内为安全区域,范围是居中左右各半个图片宽度,上方为半个图片宽度。

灰色是安全区域外的极限布局位置。可以得知,安全区域左边的 x 的取值最大应该为舞台的一半宽度 - 一个图片宽度 - 半个图片宽度,安全区域右边的 x 的取值最小值应为舞台一半宽度 + 半个图片宽度,安全区域上方的 x 的取值应为 舞台一半宽度 - 一个图片宽度(即图片左边框紧贴安全区域左侧) 至 舞台一半宽度 + 半个图片宽度(即图片右边框紧贴安全区域右侧)。

蓝色为图片在四个角最少需要漏出 1/4 的区域。所以,两侧的 y 的取值应为 负半个图片宽度舞台总高度 - 半个图片宽度

  1. 编写辅助函数

因为图片都散列排布,所以会大量用到 Math.random 进行随机取值,所以随意取值应写入辅助函数当中。同时,生成图片排布的位置取值范围应保存为一个常量,同时生成函数也应写入辅助函数中:

随机数的生成:

/**
 * 
 * @param {Int} low 随机数最小值,若只有 low 则为最大值,最小值为 0。
 * @param {Int} height 随机数最大值,若不传,则 low 为最大值。
 */
const random = (low, height) => {
    if (low !== undefined && height !== undefined) {
        return ~~(Math.random() * (height - low) + low)
    } else if (low !== undefined) {
        return ~~(Math.random() * low)
    } else {
        return Math.random()
    }
}

// 随机生成位置
const pos = (low, height) => random(low, height)

// 随机生成一个角度
const deg = () => ~~(Math.random() * 30 * (random() > 0.5 ? 1 : -1))

export default {
    pos,
    deg,
    random
}

取值范围的生成:

/**
 * 生成图片位置范围函数
 * 传入参数依次为 舞台宽,舞台高,图片宽
 */
export default (stageWidth, stageHeight, imgWidth, imgHeight) => {
    // 保存舞台一半的宽高,和图片的一半宽高
    let stageHalfWidth = stageWidth / 2,
        stageHalfHeight = stageHeight / 2,
        imgHalfWidth = imgWidth / 2,
        imgHalfHeight = imgHeight / 2
    // 上方图片放置范围
    let top = {
        x: [
            stageHalfWidth - imgHalfWidth,
            stageHalfWidth + imgHalfWidth
        ],
        y: [
            0 - imgHalfHeight,
            stageHalfHeight - imgHeight
        ]
    },
    // 居中图片的位置
    center = {
        left: stageHalfWidth - imgHalfWidth,
        top: stageHalfHeight - imgHalfHeight
    },
    // 两边图片的放置范围
    aside = {
        leftX: [
            0 - imgHalfWidth,
            stageHalfWidth - imgHalfWidth * 3
        ],
        rightX: [
            stageHalfWidth + imgHalfWidth * 2,
            stageWidth - imgHalfWidth
        ],
        y: [
            0 - imgHalfHeight,
            stageHeight - imgHalfHeight
        ]
    }
    return {
        top,
        center,
        aside
    }
}
  1. 编写组件

分为三个组件:舞台,图片,底部控制器。

详细的组件实现请移步至 GitHub 中查看,这里只说下现在的 React 和视频中不一样的地方:

  1. 脚手架的运行命令。

    create-react-app 这个脚手架的命令和视频中有点类似,其中,已经移除 grunt 工作流,全部使用 webpack 代替,相对应的 npm 命令为:

    grunt serve ==> npm run start
    grunt build ==> npm run build
    
  2. 组件的申明。

    在旧版(即视频教程中)的组件的申明是 React.createClass({..}) 。而目前版本(即 GitHub 中的 React 16)是通过下列的方式申明的:

    import React from 'react'
    
    class CustomComponent extends React {
      constructor(props) {
        super(props)
      }
    
      render() {
        return <h1>Hello React</h1>
      }
    }
    
  3. 组件事件的绑定。

    在目前版本的 React,要给组件绑定上处理时间,需要在 constructor 中进行 .bind(this) 操作。可移步至 官方文档 进行了解。

    import React from 'react'
    
    class CustomComponent extends React {
      constructor(props) {
        super(props)
        // This binding is necessary to make `this` work in the callback
        this.handleClick = this.handleClick.bind(this);
      }
    
      handleClick(e) {
        console.log('clicked.')
      }
    
      render() {
        return <h1>Hello React</h1>
      }
    }
    
  4. state 的修改和申明。

    在视频教程中,舞台在 render 的时候,才去初始化 state,其实应该在 constructor 中申明并初始化 state。

    class CustomComponent extends React {
      constructor(props) {
        super(props)
        this.state = {
          imgRange: ImageArray.map(() => {
            return {
              pos: {
                top: 0,
                left: 0
              },
              rotate: 0,
              isInverse: false,
              isCenter: false
            }
          })
        }
      }
    }
    
  5. json-loader

    最新的 webpack 已经支持 json-loader 了,不需要手动去进行额外的配置。

  6. 引入图片的路径

    需要将图片放在 public 文件夹中,才能正常引入。

### 结语

该项目作为 React 的初级入门项目是挺不错的,推荐大家可以去观看教学视频。