• 目前react搭配webpack使用就像豆浆配油条一样常见,我主要总结一下开始一个react项目需要安装的一些插件以及需要配置的webpack,比如支持JSX语法,ES6语法,打包js,支持eslint代码检查等等。

    前言

    搭一个脚手架真不是一件容易的事,之前为了学习webpack是怎么配置的选择自己搭建开发环境,折腾了好几天总算对入口文件、打包输出、JSX, es6编译成es5、css加载、压缩代码等这些基础的东西有了一个大体的了解。后来有一次组内分享技术,我作死的选择了webpack,为了看起来高大上又去折腾它的按需加载、怎样处理第三方插件、拆分CSS文件、利用Happypack实现多进程打包等等。彻底把自己搞晕了。再后来接手了一个紧急的项目,实在来不及去折腾webpack了,就选择使用react官方推荐的脚手架create-react-app,这个脚手架确实搭的非常完善,几乎不需要我们修改配置,我也研究了一下它的配置,准备从零开始搭建一个react+webpack的开发环境,配置从简单到复杂,由于内容较多,我将分为几篇文章讲述,这是第一篇。
    另外,热更新我单独写成一篇文章了,当你修改一次代码就需要手动启动服务器,然后你烦了的时候,你可以先去把热更新配置好再回来继续:开始一个React项目(二) 彻底弄懂webpack-dev-server的热更新

    初始化

    先贴出项目结构

    my-app/
      |
      ---  README.md
      --- package.json
      --- webpack.config.js
      --- public/
           |
           --- index.html(模板文件)
           --- favicon.ico(网站图标)
       --- src/(项目文件都在这里)
          |
           --- index.js(入口文件)
           --- pages/ (页面)
           --- components/(抽离的公用组件)
           --- css/
           --- js/
           --- images/

    一开始最重要的需要你建好的文件是public/index.htmlsrc/index.js

    新建一个项目,使用npm init初始化生成一个package.json文件。可以全部回车,后面反正是可以修改的。

    安装webpack: npm install webpack --save-dev

    全局安装: npm install webpack -g(全局安装以后才可以直接在命令行使用webpack)

    一个最简单的webpack.config.js文件可以只有entry(入口文件)和output(打包输出路径)
    新建webpack.config.js

    const path = require('path');
    
    module.exports = {
        entry: './src/index.js', //相对路径
        output: {
            path: path.resolve(__dirname, 'build'), //打包文件的输出路径
            filename: 'bundle.js' //打包文件名
        }
    }

    新建入口文件 src/index.js

    function hello() {
        console.log('hello world');
    }

    好了这就够了,我们已经可以运行这个项目了,打开命令窗口试一下:webpack

    编译成功了,项目根目录下已经生成好build/bundle.js文件了,bundle.js文件前面的几十行代码其实就是webpack对怎么加载文件,怎么处理文件依赖做的一个声明。
    我们可以将启动wepback的命令写到package.json中并添加一些有用的参数:

    package.json

    "scripts": {
        "start": "webpack --progress --watch --hot"
      },

    progress是显示打包进程,watch是监听文件变化,hot是启动热加载,更多命令行参数详见:webpack cli
    以后只需要执行npm start就可以了。

    添加模板文件index.html

    配置react项目最重要的两个文件是入口文件(这里是src/index.js)和html模板文件(这里是public/index.html),入口文件是整个项目开始运行的地方,模板文件是构建DOM树的地方,相信有一部分小伙伴在网上看到的教程是直接在打包路径build里面建一个index.html,然后手动或者使用html-webpack-plugin将打包后的js引入,这样存在一个问题是build本身是打包路径,而这个路径的所有文件都不应该是我们手动去添加的,甚至包括这个文件夹也不是我们事先建好的。所以最好是按照create-react-app的方式,将这类不需要被webpack编译的文件放到public路径下。

    public/index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>My App</title>
    </head>
    <body>
        <div id="root"></div>
    </body>
    </html>

    现在要让webpack知道这就是我们的html入口文件,并且我们不需要手动引入打包后的js文件,需要安装html-webpack-plugin:

    npm install html-webpack-plugin --save-dev

    webpack.config.js

    const HtmlWebpackPlugin = require('html-webpack-plugin');
    module.exports = {
        plugins: [
            new HtmlWebpackPlugin({
                template: './public/index.html', //指定模板路径
                filename: 'index.html', //指定文件名
            })
        ]
    }

    plugins是用于扩展webpack功能的,它帮助webpack构建项目,比如上面的html-webpack-plugin自动生成模板文件,以及后面用到的分离CSS等。

    这里提示一下生成的HTML路径就是output.path指定的路径,不仅如此,像extract-text-webpack-plugin分离CSS文件打包的文件路径也是这个路径。
    重新运行一下:npm start
    现在可以看到build路径下已经生成好了一个index.html文件,并且这个文件已经引入了bundle.js文件了。

    开始React项目

    安装: npm install react react-dom --save-dev,现在来修改我们的入口文件
    src/index.js

    import React, { Component } from 'react';
    import ReactDom from 'react-dom';
    
    class App extends Component {
        render() {
            return <h1> Hello, world! </h1>
        }
    }
    
    ReactDom.render(
        <App />,
        document.getElementById('root')
    )

    别着急运行,react里面的JSX语法普通浏览器可解析不了,需要安装babel来解析:
    npm install babel babel-cli babel-loader --save-dev
    再安装两个分别用于解析es6和jsx语法的插件:
    npm install babel-preset-env babel-preset-react --save-dev

    注:以前编译es6以上语法用的是babel-preset-es2015,现在是时候说再见了,babel-preset-env是一个更定制化的插件,你可以指定你要兼容的浏览器版本,这样babel会选择编译该版本不支持的语法而不是全部编译成旧的语法,具体配置参见:babel-preset-env

    webpack.config.js

    module.exports = {
    ...
      module: {
            loaders: [ //配置加载器
                {
                    test: /\.js$/, //配置要处理的文件格式,一般使用正则表达式匹配
                    loader: 'babel-loader', //使用的加载器名称
                    query: { //babel的配置参数,可以写在.babelrc文件里也可以写在这里
                        presets: ['env', 'react']
                    }
                }
            ]
        }
    }

    webpack最重要的配置都在modules(模块)里,loaders(加载器)是处理源文件的,后面你会看到,loader可以处理不同的js(jsx, es6等)编译成js,less等编译成css,将项目中引用的图片等静态资源路径处理成打包以后可以正确识别的路径等。

    现在试着运行一下,没有报错的话,直接双击打开build/index.html就可以看到hello world!了。

    加载和解析CSS样式

    我们以前写CSS大致是两种方式,一写在html里,二写在CSS文件里。现在我们没有html只有JSX了,JSX通俗一点理解就是可以在js里面写html,所以我们如果要在jsx里面写css,就是在js里面写css,写过RN的小伙伴应该对这种写法比较熟悉。

    //方式一:
    const styles = {
        container: {
            textAlign: 'center',
            marginTop: '50px'
        },
        containerBtn: {
            margin: '0 20px',
            backgroundColor: '#dde18e'
        }
    }
    //使用:
    <div style={styles.container}>
            <button style={styles.containerBtn}>count+1</button>
    </div>  
    
    //方式二:
    <div style={{textAlign: 'center', marginTop: '50px'}}>
    
    </div>  

    而如果想在JSX文件里面像我们以前的用法一样去引入CSS文件,就只能使用import语句,但是import引入的都会被当做js处理,所以,我们需要对css文件做一个转换。这就需要css-loaderstyle-loader,其中css-loader用于将css转换成对象,而style-loader则将解析后的样式嵌入js代码。
    安装:npm install style-loader css-loader --save

    webpack.config.js:

    loaders: [
                {
                    test: /\.css/,
                    loader: 'style-loader!css-loader'
                },
            ]

    使用方式三:

    //index.css
    .container {
        text-align: center;
        margin-top: 40px;
    }
    
    //index.js
    import '../css/index.css
    
    <div className="container">
    </div>

    需要注意用className而不是class

    单独编译CSS文件(只在生产环境配置)

    一般发布到线上以后,为了加载速度更快会把CSS和JS打包到不同的文件中,使用extract-text-webpack-plugin插件可以分离CSS。而其实,开发的时候是不需要单独编译CSS文件的。如果你在开发环境加了这个,又配置了热更新,那么你会发现CSS发生变化时热更新用不了了,所以建议开发环境就不要配置这个了。
    npm install extract-text-webpack-plugin --save

    webpack.config.js文件:

    const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin');
    
    module.exports = {
        //...
        module: {
            loaders: [
                {
                    test: /\.css/,
                    use: ExtractTextWebpackPlugin.extract({
                        fallback: "style-loader",
                        use: "css-loader"
                    })
                }
            ]
        },
        plugins: [
            new HtmlWebpackPlugin({
                template: './public/index.html',
                filename: 'index.html'
            }),
            new ExtractTextWebpackPlugin("bundle.css")
        ],
    };

    使用PostCSS或者Less, Sass等CSS工具

    Less或Sass想必大家都非常熟悉了,PostCSS可能有的小伙伴没有用过,我也是在create-react-app的配置里第一次见到,然后就去搜了一下,发现它挺强大的。它不是什么预处理后处理语言,而是一个平台,这就像Node一直宣称的那样:我是平台!我是平台!我是平台!既然是个平台,那我们就可以在平台上做很多事情,比如说:检查CSS(像eslint检查js那样)、添加浏览器前缀(该平台目前最火的插件)、使用未来的CSS语法(大概就像现在的花呗??)、函数式语法(类似Sass语法)等等。目前像阿里爸爸,维基百科等都在使用它。我觉得虽然官方介绍了很多插件,但我们用其中的几个就可以了。

    之前写过Sass或Less的童鞋估计会问:既然它是个平台那我可以在它上面写Sass(Less)吗?答案是可以的,另外,它也有一个类似于Sass语法的插件,在它上面配置起来非常容易,所以,怎么选择看你咯。

    安装:npm install postcss-loader --save
    安装一些你需要的工具:npm install autoprefixer precss postcss-flexbugs-fixes --save

    注:autoprefixer是自动添加浏览器前缀的插件,precss是类似Sass语法的css插件,postcss-flexbugs-fixes是修复了flex布局bug的插件,具体会有哪些bug你可以自行查看

    webpack.config.js文件:

    {
        test: /\.css$/,
        use: [
            {
                loader: 'style-loader',
            },
            {
                loader: 'css-loader',
                options: {
                    importLoaders: 1,
                }
            },
            {
                loader: 'postcss-loader',
                options: {
                    plugins: () => [
                        require('autoprefixer'),
                        require('precss'),
                        require('postcss-flexbugs-fixes')
                    ]
                }
    
            }
        ]
    },

    测一下配置生效了没有
    src/css/style.css:

    $mainColor: #8ce7b4;
    $fontSize: 1rem;
    
    @keyframes rotate {
        0%      {transform: rotate(0deg);left:0px;}
        100%    {transform: rotate(360deg);left:0px;}
    
    }
    button {
        background: $mainColor;
        font-size: calc(1.5 * $fontSize);
    }
    .container .logo {
        animation: rotate 10s linear 0s infinite;
    }

    image.png
    可以看到已经自动帮我们添加了前缀以及可以使用sass语法了,而且这是在css文件里直接写,不需要建其他后缀的文件。
    如果你非要用less或者sass,也可以,但我还是会建议你保留postcss,毕竟它有很多有用的插件,只是去掉precss即可。安装:npm less less-loader --save

    {
        test: /\.(css|less)$/, 
        use: [
               //...
            {
                loader: 'postcss-loader',
                options: {
                    plugins: () => [
                        require('autoprefixer'),
                        require('postcss-flexbugs-fixes')
                    ]
                }
            },
            {
                loader: 'less-loader',
            }
        ]
    },

    好了,现在你可以写less了。

    加载图片资源

    我们知道webpack打包会将所有模块打包成一个文件,而我们在开发项目时引入图片资源的时候是相对于当前文件的路径,打包以后这个路径是错误的路径,会导致引入图片失败,所以我们需要一个处理静态资源的加载器,url-loader和file-loader。我看到网上说url-loader是包含了file-loader的功能的,所以我们只需要下载url-loader就可以了,但是我下载完以后它却提示我url-loader依赖file-loader,并且运行项目会报错,所以我又下载了file-loader。url-loader有一个功能是对于指定大小以下的图片会转成base64,这样可以有效减少http请求。
    安装:npm install file-loader url-loader --save

    webpack.config.js

    {
        test: [/\.gif$/, /\.jpe?g$/, /\.png$/],
        loader: 'url-loader',
        options: {
          limit: 10000, //1w字节以下大小的图片会自动转成base64
        },
    }

    使用图片有两种情况,一在CSS里面设置背景,二在JSX里面设置背景或<src>
    CSS里面和以前的使用方式一样,假如你的目录结构长这样:

    src
      |
      ---pages/
      --- css/
      --- images/

    那么在CSS文件里引入背景图的路径就为:

    .container {
        background: url('../images/typescript.jpeg') no-repeat center / contain;
    }

    在JSX里面引入图片有几种方式:(该页面在pages/下)

    //方式一:
    import tsIcon from '../images/typescript.jpeg';
    
    //方式二:
    const tsIcon = require('../images/typescript.jpeg');
    
    //使用方式一:
    <img src={tsIcon} alt="" />
    
    //使用方式二:
    <div style={{background: `url(${tsIcon}) no-repeat`}}></div>
    
    //使用方式三:
    const styles = {
        test: {
            background: `url(${tsIcon}) no-repeat center`
        }
    }
    
    render() {
      return (
        <div style={styles.test}></div>
      )
    }

    另外,你也可以测试一些小的icon,看看是不是转换成了很长的一串字符串。

    配置ESLint

    js的松散真的是体现在方方面面,现在除了有TypeScript这种一心想用C#风格帮助js走上人生巅峰的语言,也有ESLint这种控制规则从娃娃抓起的工具,ESLint的创始人可是js红皮书的作者,所以,赶紧用起来吧,这样你就完全不必和队友争论到底Tab键设置为4还是为2,加不加分号等一系列问题啦。
    安装:npm install eslint eslint-plugin-react eslint-loader --save

    注:可以全局安装eslint,这样你才可以直接在命令行使用eslint xxx,如果选择全局安装eslint那么你使用的任何插件或可分享的配置也都必须在全局安装。如果选择本地安装,命令行使用应该是./node_modules/.bin/eslint xxx
    eslint-plugin-react是检查react项目的插件,eslint-loader是webpack需要的加载器插件。

    webpack.config.js

    loaders: [
        {
            test: /\.js$/,
            loader: 'babel-loader',
            query: {
                presets: ['env', 'react'], //babel编译es6以上语法以及jsx语法
                plugins: ["react-hot-loader/babel"]
            }
        },
        {
            test: /\.js$/,
            enforce: 'pre', //加载器的执行顺序,不设置为正常执行,pre(前)|post(后),eslint是检查代码规范,应该在编译前就执行
            loader: 'eslint-loader',
        },
    ]         

    接下来需要配置ESLint规则,为了满足大家“稀奇古怪”的代码风格,ESLint遵循各种规则自定义的原则,所以,前面我说有了它就可以避免因代码风格不同而与队友发生争执的问题是不准确的,因为,我们还是要制定规则啊。对于我等小菜鸟来说,遵守别人的规则会比自己制定好一些,因为,怕你对自己太好了。
    我们先来看看如何配置吧,采用命令行来初始化:eslint --init,如果是本地安装的话:./node_modules/.bin/eslint --init
    遇到的第一个问题:你喜欢怎么配置你的ESLint呢?
    image.png

    1. 根据你喜欢的方式来制定规则,你需要回答一些问题。
    2. 使用当下流行的代码规则。
    3. 根据你的js文件生成一些规则。

    使用当下流行的代码规则就是用大公司制定的一套规则啦,这里只有三个选项:
    image.png

    如果你都不喜欢,没关系,这里有很多款式供你选择,比如eslint-config-react-app就是create-react-app的代码规则。据说目前最常用的是Airbnb,它也被称为史上最严规则,选择这个回车,接着回答问题:是否使用React,希望生成的eslint文件格式是什么,我选择的是javascript,Airbnb需要安装一些插件,耐心等待就好。
    好了,运行一下代码,没有意外的话你的代码肯定有很多报错,反正我一共就两js文件,加起来七十多行代码,报了九十多个错,哈哈哈哈,让我先冷静冷静换个姿势,选择自定义规则,别误会,我是打算另外写一篇文章专门来解决我那九十多行的报错的,写在这里篇幅有点太长了。

    现在选择第一种方式,自定义代码规则,ESLint附带了一些默认规则帮你起步,具体查看默认规则列表,打勾的表示已经启用的规则,然后另外还需要你回答一些问题:
    image.png
    这里有几个要注意的选项:

    注意事项一:indentation(缩进)是用tab还是空格
    意思不是你缩进的时候是按空格键缩进还是tab键缩进,或者说很多时候缩进是编辑器做的事情,我们要做的是告诉编辑器是用哪种方式,而怎么看的出来用的是什么呢?以sublime编辑器为例,当你选中代码的时候,缩进的样式就出来了:
    image.png
    其中,..是空格,—是tab,如何修改文件的缩进风格呢?sublime编辑器是选择View–> Indentation,首先,你可以选择tab的宽度是多少,一般是2,4,然后,如果你想修改当前已有的文件缩进风格,选择 Convert Indentation to Tabs或者 Convert Indentation to Spaces,这样,整个文件的缩进风格就统一了,并且,以后你新建的文件也会按照这个风格,但是!你已有的文件风格不会变,你需要手动去每个文件下修改。所以,配置这种检查工具肯定是越早越好,等你写了一大半了再跑回来配置看到几千个错误都是很正常的,而这时候,估计你会选择放弃。

    注意事项二:line endings 选择
    了解Windows和Unix系统的童鞋都知道,这俩系统的行尾结束符不一样,Unix的行尾是两个字符:”\r\n”,Windows的行尾是一个字符:”\n”,而如果假如你在mac里写代码这里却选择了Windows,你就会看见Expected linebreaks to be 'CRLF' but found 'LF',同理反过来也一样,所以不要选错了。

    注意事项三:semicolons(分号)的选择
    分号在js里面实在是个很随意的东西,有的人喜欢打,有的人不喜欢打,有的人喜欢打一部分,但是这个选择却引起了很多争议,有人认为,虽然js有自动分号插入 (ASI)的功能,但总是依赖js去帮我们打分号是不可靠的,首先,js也会累是吧,其次,有些地方js也不知道该打在哪里,然后它就会乱打,然后你懂的,你就会被喷。有人认为,不打分号可以节省编译时间,而且,看起来很棒。然后就有一群和事佬跳出来说:我可以在可能会引起js困惑的地方打分号,在js可以自动引入分号的地方省略,嗯,应该说我们大部分人都属于这一类人。
    在react里面,如果选择了始终使用分号就会有一些比较困惑的地方,比如:

    add() {
            this.setState((preState) => {
                return{
                    count: preState.count + 1
                }
            })
        }
    render() {
      return (
        <div>hello </div>  
      )
    }

    那么,ESLint会报return{}后缺少分号,this.setState({})后缺少分号,甚至render里的return()后也缺少分号,而这应该是大部分人都不会选择加分号的地方,所以,始终使用分号这个选项在react项目里恐怕不是那么适用。不过,你是自由的,你的代码风格由你决定。

    现在你应该有能力解决Missing semicolon (或Extra semicolon )Expected indentation of 1 tab but found 4 spacesMixed spaces and tabs等这种问题了。
    但是ESLint依然对react非常不友好啊,比如:'React' is defined but never used,或者是引入其他组件也会报这个警告,当然,引入了又没有用或者根本没有引入某个组件,报了警告是非常正常的,但是我们明明用到了引入的东西它还是报警告这就说不过去了,修改.eslintrc.js文件:

        "extends": [
            "eslint:recommended",
            "plugin:react/recommended" //增加
        ],

    这绝对是最万能的解决办法!!相信大家以前用过这种写法:

    "rules": {
            //...
            "react/jsx-uses-react": 1,
        }

    但这个只能解决'React' is defined but never used这个错误,如果是引入其他组件比如import Home from './pages/Home'依然会报'Home' is defined but never used,而这时候你还需要添加一句:"no-unused-vars": 1才可以,所以用第一种方法是最好的。

    写在最后

    我把该项目放在github上了。虽然这是webpack配置的第一篇,但是为了开发方便,我把webpack-dev-server的热更新配置也放进来了,如果你对热更新有疑问可以阅读开始一个React项目(二) 彻底弄懂webpack-dev-server的热更新

    展开全文
  • 从头开始建立一个React App - 项目基本配置 npm init 生成 package.json 文件. 安装各种需要的依赖: npm install  --save react - 安装React. npm install  --save react-dom 安装React Dom,这个包是用来处理...

    从头开始建立一个React App - 项目基本配置
    npm init 生成 package.json 文件.
    安装各种需要的依赖:
    npm install
     --save react - 安装React.
    npm install
     --save react-dom 安装React Dom,这个包是用来处理virtual DOM。这里提一下用React Native的话,这里就是安装react-native。
    npm install
     --save-dev webpack - 安装Webpack, 现在最流行的模块打包工具.
    npm install
     --save-dev webpack-dev-server - webpack官网出的一个小型express服务器,主要特性是支持热加载.
    npm install
     --save-dev babel-core - 安装Babel, 可以把ES6转换为ES5,注意Babel最新的V6版本分为babel-cli和babel-core两个模块,这里只需要用babel-cor即可。
    安装其他的babel依赖(babel真心是一个全家桶,具体的介绍去官网看吧..我后面再总结,这里反正全装上就是了):
    npm install
     --save babel-polyfill - Babel includes a polyfill that includes a custom regenerator runtime and core.js. This will emulate a full ES6 environment
    npm install
     --save-dev babel-loader - webpack中需要用到的loader.
    npm install
     --save babel-runtime - Babel transform runtime 插件的依赖.
    npm install
     --save-dev babel-plugin-transform-runtime - Externalise references to helpers and builtins, automatically polyfilling your code without polluting globals.
    npm install
     --save-dev babel-preset-es2015 - Babel preset for all es2015 plugins.
    npm install
     --save-dev babel-preset-react - Strip flow types and transform JSX into createElement calls.
    npm install
     --save-dev babel-preset-stage-2 - All you need to use stage 2 (and greater) plugins (experimental javascript).
    打开 package.json 然后添加下面的scripts:

    "scripts": {
     "start": "webpack-dev-server --hot --inline --colors --content-base ./build",
     "build": "webpack --progress --colors"
    }
    命令行输入 npm start 将要启动webpack dev server.

    命令行输入 npm build 将会进行生产环境打包.

    启动webpack

    Webpack是我们的打包工具,在我们的开发环境中具体很重要的作用,具有很多非常便捷的特性,尤其是热加载hot reloading. webpack.config.js 是如下所示的webpack的配置文件. 随着app的不断变化,配置文件也会不断的更新,这里我们就用默认的webpack.config.js来命名这个配置文件,假如你用别的名字比如webpack.config.prod.js那么上面的脚本build就需要相应的改变指定相应的配置文件名字:"build":
     "webpack webpack.config.prod.js --progress --colors"

    var webpack = require('webpack');
    module.exports = {
     entry: './src/app.js',
     output: {
         path: __dirname + '/build',
         filename: "bundle.js"
     },
     module: {
         rules: [{
             test: /\.js$/,
             exclude: /node_modules/,
             loader: 'babel-loader',
             query: {
                 plugins: ['transform-runtime'],
                 presets: ['es2015', 'react', 'stage-2']
             }
         }, {
             test: /\.css$/,
             loader: "style-loader!css-loader"
         }]
     }
    };
    OK,我们项目的基本配置终于完成了,是时候开始写Reac代码了.
    React 基础 - 建立你的第一个Component
    在上面的项目的基本配置基础上,我们开始书写React的第一个组件来熟悉React的写法与组件思想。

    首先我们在项目根目录中新建一个 index.html 文件。 在这个基础工程中, 我们使用bootstrap的样式,直接引入一个cdn即可. 然后添加一个html标签 <div id="app"></div>,我们的app就会注入到这个div中。 最后再引入 <script src="bundle.js"></script>,这是最后打包生成的js代码。

    以下是完整的代码:

     <!DOCTYPE html>
     <html lang="en">
     <head>
       <meta charset="UTF-8">
       <title>Document</title>
       <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
     </head>
     <body>
       <div id="app"></div>
       <script src="bundle.js"></script>
     </body>
     </html>

    展开全文
  • 从零开始构建react项目
  • 从头开始建立一个React App - 项目基本配置npm init 生成 package.json 文件.安装各种需要的依赖:npm install --save react - 安装React.npm install --save react-dom 安装React Dom,这个包是用来处理virtual DOM。...

    从头开始建立一个React App - 项目基本配置

    1. npm init 生成 package.json 文件.
    2. 安装各种需要的依赖:
    • npm install --save react - 安装React.
    • npm install --save react-dom 安装React Dom,这个包是用来处理virtual DOM。这里提一下用React Native的话,这里就是安装react-native。
    • npm install --save-dev webpack - 安装Webpack, 现在最流行的模块打包工具.
    • npm install --save-dev webpack-dev-server - webpack官网出的一个小型express服务器,主要特性是支持热加载.
    • npm install --save-dev babel-core - 安装Babel, 可以把ES6转换为ES5,注意Babel最新的V6版本分为babel-cli和babel-core两个模块,这里只需要用babel-cor即可。
      • 安装其他的babel依赖(babel真心是一个全家桶,具体的介绍去官网看吧..我后面再总结,这里反正全装上就是了):
      • npm install --save babel-polyfill - Babel includes a polyfill that includes a custom regenerator runtime and core.js. This will emulate a full ES6 environment
      • npm install --save-dev babel-loader - webpack中需要用到的loader.
      • npm install --save babel-runtime - Babel transform runtime 插件的依赖.
      • npm install --save-dev babel-plugin-transform-runtime - Externalise references to helpers and builtins, automatically polyfilling your code without polluting globals.
      • npm install --save-dev babel-preset-es2015 - Babel preset for all es2015 plugins.
      • npm install --save-dev babel-preset-react - Strip flow types and transform JSX into createElement calls.
      • npm install --save-dev babel-preset-stage-2 - All you need to use stage 2 (and greater) plugins (experimental javascript).
    1. 打开 package.json 然后添加下面的scripts:
    "scripts": {
      "start": "webpack-dev-server --hot --inline --colors --content-base ./build",
      "build": "webpack --progress --colors"
    }
    

    命令行输入 npm start 将要启动webpack dev server.

    命令行输入 npm build 将会进行生产环境打包.

    1. 启动webpack

    Webpack是我们的打包工具,在我们的开发环境中具体很重要的作用,具有很多非常便捷的特性,尤其是热加载hot reloading. webpack.config.js 是如下所示的webpack的配置文件. 随着app的不断变化,配置文件也会不断的更新,这里我们就用默认的webpack.config.js来命名这个配置文件,假如你用别的名字比如webpack.config.prod.js那么上面的脚本build就需要相应的改变指定相应的配置文件名字:"build": "webpack webpack.config.prod.js --progress --colors"

    var webpack = require('webpack');
    module.exports = {
      entry: './src/app.js',
      output: {
          path: __dirname + '/build',
          filename: "bundle.js"
      },
      module: {
          rules: [{
              test: /\.js$/,
              exclude: /node_modules/,
              loader: 'babel-loader',
              query: {
                  plugins: ['transform-runtime'],
                  presets: ['es2015', 'react', 'stage-2']
              }
          }, {
              test: /\.css$/,
              loader: "style-loader!css-loader"
          }]
      }
    };
    
    
    1. OK,我们项目的基本配置终于完成了,是时候开始写Reac代码了.

    React 基础 - 建立你的第一个Component

    在上面的项目的基本配置基础上,我们开始书写React的第一个组件来熟悉React的写法与组件思想。

    1. 首先我们在项目根目录中新建一个 index.html 文件。 在这个基础工程中, 我们使用bootstrap的样式,直接引入一个cdn即可. 然后添加一个html标签 <div id="app"></div>,我们的app就会注入到这个div中。 最后再引入 <script src="bundle.js"></script>,这是最后打包生成的js代码。

      以下是完整的代码:

      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <title>Document</title>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
      </head>
      <body>
        <div id="app"></div>
        <script src="bundle.js"></script>
      </body>
      </html>
      
    2. 建立一个新的文件夹 src. 我们app的大部分代码都将放在这个文件夹里面。在 src 中建立 app.js,作为React App的根组件, 其他所有的组件都会注入到这个跟组件中。

    3. 首先我们需要导入react,现在都已经用ES6的语法, import React from 'react'; , 然后我们要引入react-dom. 这里面有react中最重要的一个虚拟dom的概念.引入代码:import ReactDOM from 'react-dom';

    4. 现在需要引入的依赖都已经完毕我们可以写第一个组件了:

      class App extends React.Component {
        render(){ // Every react component has a render method.
          return( // Every render method returns jsx. Jsx looks like HTML, but it's actually javascript and functions a lot like xml, with self closing tags requiring the `/` within the tag in order to work propperly
            <div>
              Hello World
            </div>
          );
        }
      }
      

      注意这里"Hello World"写在 div中. 所有的jsx代码都需要写在一个父div中.

    5. 最后我们需要把我们写好的组件render给Dom,这里就需要用到 ReactDOM.render 方法.

      App.js 的下面添加: ReactDOM.render(<App />, document.getElementById('app'));

      第一个参数就是我们App的根组件, 写作<App />的形式. 第二个参数就是我们的APP将要主要的DOM元素. 在这个项目中,就是我们在index中写的id为appdiv标签。

    Ok,我们的APP结构已经出来了,经典的hello world已经实现。马上我们就在这个基础上再实现经典的todo app。大致的原型就有一个输入框用来输入代办事项然后添加到事件列表中。事件列表中每一个代办事项被点击就会标注一条删除线表示完成,点击后面的删除按钮则会将其从列表中删除。通过完成这个APP的过程你将学会一个完整的react app的所有的基本构建块。
    

    生命周期方法和两种形式的组件构建

    我们从一些小的模块开始起步.一个组件component就是一个react app的构件块. 有两种形式的组件: 类组件(Class)和函数型组件(Functional). 在这个项目中,这两种形式的组件我们都会使用, 并且使用生命周期的钩子,同时也会使用state和props两个react中重要的属性。

    1. 首先在 src文件夹中新建components 文件夹,你的文件结构就是这样 ~/src/components

    2. 然后在components中新建文件 ToDoApp.js。 对于所有的react组件我们都需要在头部引入reactimport React from 'react';

    3. 下面我们写一个类组件. 所有的class 组件有一个render方法用来返回jsx。

      ToDoApp的class就如下所示:

      class ToDoApp extends React.Component {
        render() {
          return (
            <div>To Do App</div>
          );
        }
      }
      
    4. 为了将这个组件注入到我们的APP中, 首先我们需要输出它。 在这个组件代码底部添加 export default ToDoApp;

    5. 然后在app.js顶部我们添加 import ToDoApp from '.components/ToDoApp'; 导入组件用来代替 Hello World 。 render中替换为新的jsx代码 <ToDoApp />半闭合类型的标签即可。

    6. 然后在浏览器中你就可以看到"To Do App" 代替了原来的 "Hello World"!这样我们就完成了将第一个子组件嵌入到根组件之中了,这就是构建react app的常规模式。下面继续完善我们的组件。

    7. 返回到ToDoApp 中来构建我们的第一个代办事项列表。首先我们使用bootstrap来构建比较方便且美观。 用下面的jsx替换当前render方法中 return 中的jsx:

    <div className="row">
      <div className="col-md-10 col-md-offset-1">
        <div className="panel panel-default">
          <div className="panel-body">
            <h1>My To Do App</h1>
            <hr/>
            List goes here.
          </div>
        </div>
      </div>
    </div>
    
    1. 现在打开浏览器, 你将会看到一个标题 "My To Do App" 下面跟随一个bootstrap的panel组件里面写有 "List Goes Here",我们将在这个地方构建列表。 那么我们如何将数据存储在我们的列表中呢? 答案就是使用 state. 每一个类组件都有 state 属性,可以通过 this.state在组件任何位置获取并且用 this.setState({ key: "value" })这种方法来更新状态。但是除非必要我们比较少使用state,这里暂时先使用作为了解,后期会使用redux来管理状态。

      ToDoApp中我们可以使用许多生命周期方法的钩子, 其中一个就是componentWillMount。 这个方法的执行是在页面加载并且render方法之前。可以在其中获取列表数据,在我们的APP中直接用一个虚拟的数组提供。(值得注意的是componentWillMount会引起很多小问题,因此真实项目中尽量不要使用,而是应该用componentDidMount)。
      ToDoApprender 方法之前添加:

        componentWillMount(){ // run before the render method
          this.setState({ // add an array of strings to state.
            list: ['thing1', 'thing2', 'thing3']
          })
        };
      

      现在我们获取了一个虚拟列表,需要重点注意的就是react依赖于state和props,只有当state和props改变的时候react组件才会刷新。

    2. 现在我们添加列表到这个view里,这里不是直接简单的在里面修改jsx,而是再创建一个新的组件来构建列表,这次我们学习使用函数型组件,需要注意的是函数型组件没有生命周期方法和state属性,它仅仅是一个返回jsx的函数,并且参数是props。

      那么props到底是什么呢?props是从父组件传递进子组件的数据的名字,这是一个很重要的概念,也是react app数据传递的最典型与最推荐的方法。通常我们将数据保持在app的顶端组件,通过组件让数据流下来保证APP的精确运行。这些数据和props的一些处理可能会影响APP的运行,但是假如你按照这个课程的实践流程来做,这些影响都会很小。

      再新建一个components文件夹并在其中新建一个List.js作为我们要创建的函数型组件。用const来新建一个函数,参数名字写作props。
      函数形式如下所示:

      const List = (props) => { // we're using an arrow function and const variable type, a ES6 features
      
        return (
          <div>
            I'm a list!!!
          </div>
        )
      };
      
      export default List;
      
    3. ToDoApp.js引入 List用List 组件替换 List goes here.,写法为 <List />.现在在浏览器中就可以看到"I'm a list!!!"

      现在我们来把这个变成真实的列表,首先就需要通过props传递数据,我们把这个从state中获取的数据list通过命名为listItems的props传递,写作: <List listItems={this.state.list} /> ,现在 List 已经通过props获取了 ToDoApp中的数据。

      然后在 List 组件中我们需要render一个列表,先用下面的jsx代码代替:

      <div>
        <ul>
          {
            list // this is a variable we'll define next
          }
        </ul>
      </div>
      

      注意这个大括号,js可以在这里面执行并将返回添加到view里。首先我们定义一个列表变量:

      const list = props.listItems.map((el, i)=>(
        // All where doing here is getting the items listItems prop
        // (which is stored in the state of the parent component)
        // which is an array, and we're running the .map method
        // which returns a new array of list items. The key attribute is
        // required, and must be unique.
        <li key={i}><h2>el</h2></li>
      ));
      

      完整的组件如下:

      import React from 'react';
      
      const List = (props) => {
      
        const list = props.listItems.map((el, i)=>(
          <li key={i}><h2>el</h2></li>
        ));
      
        return (
          <div>
            <ul>
              {
                list
              }
            </ul>
          </div>
        )
      };
      
      export default List;
      
    4. 现在打开浏览器就可以看到一列列表了。接下来就是给我们的项目加入功能了,包括添加新的事项,标注事项完成和删除列表中事项。

    给APP添加功能

    1.函数型组件

    首先我们需要添加一个input元素以便可以输入代办事项。因此我们在components文件夹中新建一个Input.js,然后在其中创建并输出一个名叫Input的函数型组件。
    把下面的jsx代码粘贴到你的函数型组件return之中:

    <form>
      <div
        className="form-group">
        <label
          htmlFor="listInput">
          Email address
        </label>
        <input
          type="text"
          className="form-control"
          id="listItemInput"
          placeholder="Add new todo"
        />
        <button
          className="btn btn-primary">
          Add Item
        </button>
      </div>
    </form>
    

    2. Input

    现在我们的jsx没有做任何特殊的事情,仅仅是一个基本的html视图,不过我们先测试一下把其导入到ToDoApp.js,形式就是<Input/>

    这时候会发现一个输入框和按钮的视图,这个组件的静态视图已经写好了,下面就需要添加功能了。

    3. Props

    首先我们需要做的是如何获取输入框的值,因为这个输入框的值需要在其他组件中获取,所以我们并不想要在Input组件中来处理这个数据存储。事实上,在子组件中存储数据在任何时候都是不推荐的,我们应该将数据存储在app的顶端组件并且通过props传递下来。

    另一个需要记住的是即使我们目前把数据存储在了上层的 ToDoApp 组件,后期还是会用redux来代替来处理整个app的数据。这里先仅仅使用react的state来实现。

    ok,我们在ToDoAppcomponentWillMountsetState中新增一个newToDo属性用来存储输入框的值。

      componentWillMount(){
        this.setState({
          list: ['thing1', 'thing2', 'thing3'],
          newToDo: 'test'
        })
      };
    

    同样的就可以通过在<Input />上通过props传递下去。

    4. 解构(Destructuring)

    Input.js中我们通过参数props可以获得上级组件传递下来的值, 但是还可以用ES6的新特性解构来作为参数,这样看起来更加酷!

    Input组件的props参数修改为({ value })这样的参数形式,这样可以把props这个对象参数解构为一个个键值对。直接看个小例子来就很明白了:

    var props = {
      name: 'hector',
      age: 21
    }
    
    
    function log(props){
      console.log(props.name);
      console.log(props.age);
    }
    
    log(props);
    

    is the same as this:

    let props = {
      name: 'hector',
      age: 21
    }
    
    log = ({name, age}) => {
      console.log(name);
      console.log(age);
    }
    
    log(props);
    

    5. setState

    上面的newToDo仅仅是添加了一个state用来存储输入框的值,给定一个值,输入框就会显示,明显还不是我们要的效果,我们需要做的是基于输入框的值的改变来动态改变这个state。

    为了实现这个功能,我们需要再添加一个onChange方法同样利用props传进Input组件: onChange={onChange}, 然后解构参数就是({ onChange, value })

    然后在 ToDoApp.jscomponentWillMount 添加一个新的方法 onInputChange。这个方法有一个参数event, 它将要捕获用户在输入框输入的值。

    onInputChange = (event) => {
      this.setState({ newToDo: event.target.value}); // updates state to new value when user changes the input value
    };
    

    6. 添加新列表事项

    现在需要向列表中添加新的事项,也就是在提交后能把输入框的值存储并显示到列表中。我们需要再新建一个onInputSubmit的方法,参数同样是event,函数体内首先需要写 event.preventDefault(),然后用 setState 方法把新事项添加到列表数组中,但是,一定要注意我们的state应该是immutable的,这是react中必须遵循的一个准则,这样才能保证对比性与可靠性。

    为了实现这个功能, 需要用到this.setState 回调函数,参数为previousState

    this.setState((previousState)=>({
      list: previousState.list.push(previousState.newToDo)
    }))
    

    正如我上面的描述,最开始写state的时候很多人都会犯这样的错误,直接用push这样的方法,修改了state,这样就不算immutable的,我们一定要保证绝不直接修改原state。

    这里又可以用到ES6中的新特性了,扩展操作符,它通过遍历旧数组返回一个新数组,使旧的数组保持原样,这样我们就把事项添加到列表数组末尾:

    this.setState((previousState)=>({
      list: [...previousState.list, previousState.newToDo ], // the spread opperator is called by using the ... preceding the array
    }));
    

    在提交添加新事项的同时,需要将newToDo重置为''

    this.setState((previousState)=>({
      list: [...previousState.list, previousState.newToDo ],
      newToDo: ''
    }));
    

    7. 划掉事项

    是时候添加划掉事项的功能了。为了实现这个功能需要添加一个新的属性用来标注是否需要划掉,因此需要改变原来的数组为一个对象数组,每一个事项都是一个对象,一个key为item表示原来的事项内容,一个key为done用布尔值来表示是否划掉。 然后先把原来的onInputSubmit方法修改,同样要注意immutable,使用扩展操作符如下:

    onInputSubmit = (event) => {
      event.preventDefault();
      this.setState((previousState)=>({
        list: [...previousState.list, {item: previousState.newToDo, done: false }], // notice the change here
        newToDo: ''
      }));
    };
    

    属性done添加完成后就需要新增一个方法当点击事项时候来改变这个值:

    onListItemClick = (i) => { // takes the index of the element to be updated
      this.setState((previousState)=>({
        list: [
          ...previousState.list.slice(0, i), // slice returns a new array without modifying the existing array. Takes everything up to, but not including, the index passed in.
          Object.assign({}, previousState.list[i], {done: !previousState.list[i].done}), // Object.assign is a new ES6 feature that creates a new object based on the first param (in this case an empty object). Other objects can be passed in and will be added to the first object without being modified.
          ...previousState.list.slice(i+1) // takes everything after the index passed in and adds it to the array.
        ]
      }))
    };
    

    然后把这个方法通过props传递给List 组件,这里就没有使用解构参数传递,用来和Input的做对比。因为这个函数需要一个参数就是当前列表的序列号,但是肯定不能直接call这个函数否则会报错,因此使用bind方法,出入i参数:

    onClick={props.onClick.bind(null, i)}
    

    当然还有另一种方法:

    onClick={() => props.onClick(i)}
    

    然后在事项内容的span标签上添加 onClick 方法,改变当前事项的done值后,在通过判断此布尔值来进行样式的修改添加或者划掉删除线。

    <span
      style={
        el.done
        ? {textDecoration: 'line-through', fontSize: '20px'}
        : {textDecoration: 'none', fontSize: '20px'}
      }
      onClick={props.onClick.bind(null, i)}
    >
    

    8. 删除事项

    最后我们在添加删除事项的功能,这个和划掉事项非常相似,我们只需要新增一个删除按钮,然后再新增一个方法修改列表,具体代码如下:

    <button
      className="btn btn-danger pull-right"
      >
      x
    </button>
    
    deleteListItem = (i) => {
      this.setState((previousState)=>({ // using previous state again
        list: [
          ...previousState.list.slice(0, i), // again with the slice method
          ...previousState.list.slice(i+1) // the only diffence here is we're leaving out the clicked element
        ]
      }))
    };
    

    deleteListItem 方法传递到列表组件中然后在删除按钮上绑定即可,仿照上一个自己写一下就好。

    现在我们有一个完整功能的APP了,是不是感觉很cool,这个就是不用redux时候的形态了,但是你会发现当状态越来越复杂时候很繁琐,因此我们下面就要介绍redux来管理状态了。

    迁移到redux的准备工作

    截至目前我们已经学会如何用webpack和babel搭建react应用,构建类组件和函数型组件并处理state,添加功能。然而这只是基本满足一个小型应用的需求,随着app的增长,处理数据和行为会越来越吃力,这就是要引入redux的必要性。

    那么redux如何处理数据?首先,redux给你的app一个单一的state对象,与flux等根据view来划分为多个state对象正好相反。你可能会有疑问,一个单一的对象来处理一个复杂的app岂不是非常复杂?redux采用的方法是把数据处理分为reducer functionsaction creatorsactions然后组合在一起工作流线型的处理数据。

    1. 首先安装必须的依赖

    首先安装 redux and react-redux

    npm install --save redux
    npm install --save react-redux
    

    然后安装 redux middleware,这里就先安装 redux-logger,它的功能是帮助我们开发。

    npm install --save redux-logger
    

    还有一些常用的中间件,比如 redux-thunk and redux-promise, 但是在我们的这个项目中暂时先不需要,可以自行去github了解。

    2. 构建

    使用redux构建react应用一般都有一个标准的模板,可能不同模板形式上有区别,但是思想都是一样的,下面就先按照一种文件结构来构建。

    首先我们在src中新建一个文件夹redux,然后在其中新建一个文件configureStore.js,添加以下代码:

    import { createStore, applyMiddleware, combineReducers } from 'redux';
    import createLogger from 'redux-logger';
    

    createStore 是由redux提供的用来初始化store的函数, applyMiddleware是用来添加我们需要的中间件的。

    combineReducers 用来把多个reducers合并为一个单一实体。

    createLogger 就是我们这里唯一使用的一个中间件,可以console出每一个action后数据的详细处理过程,给调试带来了很大方便。

    然后添加下面代码:

    const loggerMiddleware = createLogger(); // initialize logger
    
    const createStoreWithMiddleware = applyMiddleware( loggerMiddleware)(createStore); // apply logger to redux
    

    这里暂时没有完成,需要后面的模块写完了再导入到这里继续来完成。

    3. 模块Modules

    src/redux/ 新建一个文件夹 modules。在这个文件夹中我们将存放所有的reducersaction creatorsconstants。这里我们使用的redux组织结构叫做ducks,思想就是把相关的reducersaction creatorsconstants都放在一个单独的文件中,而不是分开放在多个文件中,这样修改一个功能时候直接在一个文件中修改就可以。

    modules 文件中新建 'toDoApp.js',注意这里的命名是依据容器组件的名字来命名,这个也是规范,容易管理代码。

    现在我们可以开始创建initial statereducer function,这其实非常简单,state就是一个js对象,reducer就是js的switch语句:

    const initialState = {}; //The initial state of this reducer (will be combined with the states of other reducers as your app grows)
    
    export default function reducer(state = initialState, action){ // a function that has two parameters, state (which is initialized as our initialState obj), and action, which we'll cover soon.
      switch (action.type){
      default:
        return state;
      }
    }
    

    4. 完善Store

    现在我们已经完成了第一个reducer,可以将其添加到 configureStore.js 中去了, 导入: import toDoApp from './modules/toDoApp';

    然后用combineReducers 来组合当前的reducer,因为未来会有更多的模块加入。

    const reducer = combineReducers({
      toDoApp
    });
    

    最后在底部加入下面完整的代码:

    const configureStore = (initialState) => createStoreWithMiddleware(reducer, initialState);
    export default configureStore;
    

    Cool. We're done here.

    5. Connect

    现在我们已经有了一个reducer,那么怎么和app建立联系呢?这需要两步工作。

    前面已经讲过类组件和函数型组件,有时候也可以称为smart componentsdumb components,这里我们新增一种容器组件,顾名思义,这种组件就是作为一个容器用来给组件提供actionsstate

    下面来创建第一个容器组件,首先在 /src/ 下新增一个文件夹containers,然后再其下面新建一个文件 toDoAppContainer.js
    在文件顶部首先导入 connect 用来将容器和组件联系在一起,

    import { connect } from 'react-redux';
    import ToDoApp from '../components/ToDoApp.js'
    

    connect 这个函数被调用两次, 第一次是两个回调函数: mapStateToProps and mapDispatchToProps。 第二次是把statedispatch传入组件的时候。这里的dispatch又是什么呢?

    当我们需要在redux中发生某些行为时候,就需要调用dispatch函数传递一个action然后调用reducer这一套流程。因为我们还没有编写具体的行为,这里就暂时空白,后面再补,代码形式如下:

    function mapStateToProps(state) {
      return {
        toDoApp: state.toDoApp // gives our component access to state through props.toDoApp
      }
    }
    
    function mapDispatchToProps(dispatch) {
      return {}; // here we'll soon be mapping actions to props
    }
    

    然后在底部添加:

    export default connect(
      mapStateToProps,
      mapDispatchToProps
    )(ToDoApp);
    
    1. Provider

    redux的基本工作已经完成,最后一步就是返回到app.js 文件, 首先我们不再需要导入 ToDoApp 组件,而是用容器组件ToDoAppContainer来替代,然后需要导入 configureStore 函数和 Provider,在头部添加代码:

    import { Provider } from 'react-redux';
    import ToDoAppContainer from './containers/ToDoAppContainer';
    import configureStore from './redux/configureStore';
    

    configureStore is the function we created that takes our combined reducers and our redux middleware and mashes them all together. Let's intialize that with the following line:

    const store = configureStore();
    

    然后return的jsx中同样需要把ToDoApp 改为 ToDoAppContainer,然后需要用Provider 组件将其包裹,它的作用就是将整个app的state传递给它所包裹的容器,从而使容器组件可以获取这些state。

    <Provider store={store}> // we pass the store through to Provider with props
      <ToDoAppContainer />
    </Provider>
    

    现在整个redux的基本结构已经搭建起来,下一步就可以把整个行为逻辑代码补充进去就可以了。

    Redux Actions 和 Reducers

    搭建起redux的基本结构后,就可以填充redux的元素了,简单来说我们只需要记住四个概念, Types, Actions, Action Creators, and Reducers。然后把这些元素用ducks的文件组织结构组织起来就可以了。

    Ducks

    规则

    在module中我们需要遵循下面的代码风格和命名方式:

    1. 须用 export default 输出名为 reducer()的函数
    2. 须用 export 输出 函数形式的action creators
    3. 须用 npm-module-or-app/reducer/ACTION_TYPE
      的命名形式来命名action types,因为到后期很多reducer,不同的人协同工作难免会出现命名重复,这样子加上app和模块的前缀的话就不会出现命名冲突的问题。
    4. 须用大写的蛇形方式UPPER_SNAKE_CASE来命名action types

    Types

    这个types就是上面第三条中需要按照ducks的规范命名的常量名字,将其写在文件的顶部,当action 触发时候会传递给reducerreducer的switch语句会根据这个type来进行相应的数据处理。

    const ADD_ITEM = 'my-app/toDoApp/ADD_ITEM';
    const DELETE_ITEM = 'my-app/toDoApp/DELETE_ITEM';
    

    Actions

    Actions 就是一个至少包含type的简单的js对象,同时可以包含数据以便传递给reducer。当用户在页面上触发了某种行为,一个aciton creator将会发送acitonreducer做数据处理。

    action示例如下:

    { type: ADD_ITEM, item: 'Adding this item' }
    { type: DELETE_ITEM, index: 1 }
    { type: POP_ITEM }
    

    Action Creators

    Action creators 是创建acitons并传递给reducer的函数,它通常返回一个action对象,有时候借用thunk这样的中间件也可以返回dispatch多个actions,在我们的app中为了简化暂时不涉及这个模式。

    function addItem(item){
      return {
        type: ADD_ITEM,
        item // this is new ES6 shorthand for when the key is the same as a variable or perameter within the scope of the object. It's the same as item: item
      }
    }
    

    Reducers

    reducer是唯一可以触碰store的元素,初始值为initialState,形式上就是一个简单的switch语句,但是注意不能直接改变state,因为state是immutable。也就是说我们不能直接使用.pop or .push这些方法操作数组。

    下面是示例代码:

    const initialState = {
      list: []
    };
    
    export default function reducer(state = initialState, action){
      switch (action.type){
      case ADD_ITEM:
        return Object.assign(
          {},
          state,
          { list: [...state.list, action.item]} // here we see object.assign again, and we're returning a new state built from the old state without directly manipulating it
        )
      default:
        return state;
      }
    }
    

    概念已经介绍完毕,下面开始将原来的功能逻辑用redux重写。

    1. Initial state

    首先我们在 src/redux/modules/toDoApp中声明initialState

    const initialState = {
      list: [{item: 'test', done: false}] // just added this to test that state is being passed down propperly,
      newToDo: ''
    };
    
    export default function reducer(state = initialState, action){
      switch (action.type){
      default:
        return state;
      }
    }
    

    现在在 ToDoApp.jsrender() 方法中return之前添加console.log(this.props) 会打印出下面的对象:

    toDoApp: Object
      list: Array[1]
        0: "test"
        length: 1
        __proto__: Array[0]
      __proto__: Object
    __proto__: Object
    

    测试通过,我们就可以传递这些数据给子组件了,这里就可以把原来List组件的 listItems prop和Inputvalue prop替换掉了。

    <List
      onClick={this.onListItemClick}
      listItems={this.props.toDoApp.list}
      deleteListItem={this.deleteListItem}
    />
    <Input
      value={this.props.toDoApp.newToDo}
      onChange={this.onInputChange}
      onSubmit={this.onInputSubmit}
    />
    

    这里只是替换掉了数据,下面还需要把action也替换。

    3. Input action

    这个过程就是把我们原来在ToDoApp 组件的行为逻辑全部迁移到redux文件夹下的 toDoApp module中去。

    const INPUT_CHANGED = 'INPUT_CHANGED';
    
    export function inputChange(newToDo){
      return {
        type: INPUT_CHANGED,
        newToDo
      }
    }
    

    然后在reducer的switch中新增如下处理:

    case INPUT_CHANGED:
        return Object.assign(
          {},
          state,
          {newToDo: action.value}
        );
    

    toDoAppContainer.jsmapDispatchToProps 函数就需要返回相应的action,首先导入 inputChange, 具体代码如下:

    import { connect } from 'react-redux';
    import ToDoApp from '../components/ToDoApp.js'
    import {
      inputChange
    } from '../redux/modules/toDoApp'; // we added this
    
    function mapStateToProps(state) {
      return {
        toDoApp: state.toDoApp // gives our component access to state through props.toDoApp
      }
    }
    
    function mapDispatchToProps(dispatch) {
      return {
        inputChange: (value) => dispatch(inputChange(value)) // we added this
      };
    }
    
    export default connect(
      mapStateToProps,
      mapDispatchToProps
    )(ToDoApp);
    

    这样state和action都传递给了toDoApp然后再通过props传递给子组件就可以使用了,具体都可以看项目最终代码。

    4. 其他 actions

    其他acitons的代码模式跟上面的基本一样,这里不在赘述。

    总结

    到这里一个使用webpack打包的react+redux(ducks)的基本应用模型就出来了,虽然简单但是是我们进行更复杂项目的基础,并且有了这些基础后面的路程将会顺畅多了,一起加入react的大家庭吧。



    作者:瘦人假噜噜
    链接:https://www.jianshu.com/p/324fd1c124ad
    來源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    展开全文
  • 1、初始化项目安装插件:npm init...webpack-dev-server - webpack官网出的一个小型express服务器,主要特性是支持热加载 webpack-merge: 引用通用的配置来组合 3、安装react相关插件: npm install react react-dom...

    1、初始化项目安装插件:npm init -y
    2、安装webpack, 现在最流行的模块打包工具
    webpackcli webpack的命令工具
    webpack-dev-server - webpack官网出的一个小型express服务器,主要特性是支持热加载
    webpack-merge: 引用通用的配置来组合
    3、安装react相关插件:
    npm install react react-dom
    4、安装babel相关的插件
    Babel-loader 文件处理器,加载js文件
    @bable/core babel的核心功能
    @babel/preset-env 将es6转换为es5
    @bable/preset-react 转换react的语法
    5、安装html-webpack-plugin的插件 自动生成html文件,尤其能随着编译的变化而变化,不需要手动改变bundle.js
    6、配置webpack 方便维护和管理,在根文件夹下创建config文件,分别建立三个文件webpack.common.js(通用配置),webpack.dev.js开发环境配置,一个是生产环境的配置webpack.prod.js
    webpack.common.js
    const HtmlWebpackPlugin = require(‘html-webpack-plugin’);
    const path = require(‘path’);
    module.exports = {
    //入口设置
    entry:{
    app:’./src/index.js’,//入口文件名
    },
    //http服务的设置
    devServer:{
    port:1234,
    },
    //管理资源
    module:{
    rules:[
    {//加载js
    test:/.js$/,
    use:‘babel-loader’,
    exclude:/node_modules/
    }
    ]
    },
    //出口设置
    output:{
    filename:’[name].bundle.js’,
    path:path.resolve(__dirname,’…/dist’)
    },
    plugins:[
    new HtmlWebpackPlugin({
    template:‘public/index.html’////指定的html模板,这个会自动帮我引用下面output出口设置的文件名。
    })
    ]
    }
    webpack.dev.js
    const commmon = require(’./webpack.common.js’);
    const merge = require(‘webpack-merge’);
    module.exports = merge(commmon,{
    mode:‘development’
    });

    webpack.prod.js
    const commmon = require(’./webpack.common.js’);
    const merge = require(‘webpack-merge’);
    module.exports = merge(commmon,{
    mode:‘production’
    });
    8、既然用react和es6,那就需要配置.babelrc,就在项目的根目录下新建
    {
    “presets”: [
    “@babel/preset-env”,
    “@babel/preset-react”,
    ]}
    9、在项目的根目录下新建public,放index.htm
    //index.html

    react demo
    10、在项目的根目录下新建src文件夹,然后再新建index.js文件 import React from 'react';import ReactDOM from 'react-dom';

    ReactDOM.render(

    Hello React.js

    ,
    document.getElementById(‘root’), )

    展开全文
  • 一、核心步骤 1. 使用 npm install create-react-app 命令...1. 新建一个文件夹 react; 2. 按住shift,右键选择“在此处打开 PowerShell 窗口”(也可以通过 win+R 命令一层层进入到该目录下,或者直接用编辑器打...

    一、核心步骤

    1. 使用 npm install create-react-app 命令安装初始化工具;

    2. 使用 create-react-app myapp 命令安装React 官方脚手架。

    二、详细步骤

    1. 新建一个文件夹 react;

    2. 按住shift,右键选择“在此处打开 PowerShell 窗口”(也可以通过 win+R 命令一层层进入到该目录下,或者直接用编辑器打开文件夹);

    3. 输入命令 npm install create-react-app

    4. 继续输入命令 create-react-app myapp;其中 myapp 是项目名称,可按自己项目情况取;

    5. 加载完成之后,执行 cd myapp 进入到 myapp 目录下;(注意一定要先进入到 myapp 目录下);

    6. 执行 npm start 就可以打开脚手架页面了,打开的页面如下所示。 

    展开全文
  • 从零开始配置一个完整的 webpack react 项目
  • 根据自己最近学习react 视频,下面谈谈如何去创建一个react项目 准备工作: ES6语法、html5、css3、JSX语法 node.js 这里我们根据react官网中给出的安装react项目步骤来创建一个react项目。 打开react官网,可以是...
  • React实战-如何快速构建一个ReactNative的Demo ReactJs宣称的是一次学习就够了,意思是学习了ReactJs后,在Web端和移动端就都一样处理了。事实是否真的是这样呢?在实际的应用过程中会发现,现实并非如口号叫的那么...
  • 这是我在React Rally 2016中发表的一个演讲,在那里我通过一个简化的React实现来解释它是如何工作的。 这种简化的实现被称为Dilithium。
  • 从最近对React-Native的研究来看,我觉得是时候从0开始学它并引入到实践中了。目前我负责Android这块,之前也有半年的Js、半年的Php开发经验,曾经那时可谓样样通,样样不精(其实我还有1年的C、1年的C++编程经验,...
  • 1.下载安装NVM - 这是一个管理nodejs版本的工具:NVM下载地址 点击安装 安装好cmd检查一下:nvm -v (一下操作都是cmd中操作的) 2.设置镜像,建议使用阿里的镜像 nvm npm_mirror ...
  • 创建一个react.JS项目

    2017-12-18 16:14:30
    从零开始创建一个react.JS项目
  • 最近接到需求使用React来搭建一个较为复杂的页面,所以先基于webpack来搭建了一个简易的初始结构。项目地址:https://github.com/Kasol/React4Webpack.git 可以clone下来直接npm start运行。由于想从目录结构开始...
  • 1.首先确定自己安装了node,安装好了之后,npm也同样会有 2.全局安装create-react-app,他是react的脚手架,...3.新建自己第一个react项目 create-react-app 项目名称 4.项目结构 5.yarn start,项目启动 ...
  • 这是facebook团队的一个小项目,可以快速搭建一个react的项目结构,容易上手,非常适合像我这样的初学者。 步骤 node.js环境: 首先你得安装这个,因为后续的npm命令是基于这个的,不赘述。 直接安装: 运行npm...
  • 总结一下,一个简单的React.js应用应按照以下步骤构建: 设计组件原型和JSON API; 拆分用户界面为一个组件树; 利用React, 创建应用的一个静态版本; 识别出最小的(但是完整的)代表UI的state; 确认state的...
  • 本系列的特色是从 需求分析、API 设计和代码设计 三个递进的过程中,由简到繁地开发一个 React 组件,并在讲解过程中穿插一些 React 组件开发的技巧和心得。 为什么从表格开始呢?在企业系统中,表格是最常见但功能...
1 2 3 4 5 ... 20
收藏数 60,288
精华内容 24,115