一个react项目结构_react 项目项目结构 - CSDN
  • 从头开始建立一个React App - 项目基本配置 npm init 生成 package.json 文件.安装各种需要的依赖: npm install --save react - 安装React.npm install --save react-dom 安装React 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).
    3. 打开 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 将会进行生产环境打包.

    4. 启动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为app的 div标签。

    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>
    8. 现在打开浏览器, 你将会看到一个标题 "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)。
      在 ToDoApp中 render 方法之前添加:

         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组件才会刷新。

    9. 现在我们添加列表到这个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;
    10. 在 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;
    11. 现在打开浏览器就可以看到一列列表了。接下来就是给我们的项目加入功能了,包括添加新的事项,标注事项完成和删除列表中事项。

    给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,我们在ToDoApp的 componentWillMountsetState中新增一个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 state和 reducer 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的元素了,简单来说我们只需要记住四个概念, TypesActionsAction 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.js的 render() 方法中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.js 的 mapDispatchToProps 函数就需要返回相应的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的大家庭吧。

    展开全文
  • 目录说明 │ .babelrc #babel配置文件 │ package-lock.json │ package.json │ README.MD │ webpack.config.js #webpack生产配置文件 │ webpack.dev.config.js #webpack...

    目录说明
    │ .babelrc #babel配置文件
    │ package-lock.json
    │ package.json
    README.MD
    │ webpack.config.js #webpack生产配置文件
    │ webpack.dev.config.js #webpack开发配置文件

    ├─dist
    ├─public #公共资源文件
    └─src #项目源码
    │ index.html #index.html模板
    │ index.js #入口文件

    ├─component #组建库
    │ └─Hello
    │ Hello.js

    ├─pages #页面目录
    │ ├─Counter
    │ │ Counter.js
    │ │
    │ ├─Home
    │ │ Home.js
    │ │
    │ ├─Page1
    │ │ │ Page1.css #页面样式
    │ │ │ Page1.js
    │ │ │
    │ │ └─images #页面图片
    │ │ brickpsert.jpg
    │ │
    │ └─UserInfo
    │ UserInfo.js

    ├─redux
    │ │ reducers.js
    │ │ store.js
    │ │
    │ ├─actions
    │ │ counter.js
    │ │ userInfo.js
    │ │
    │ ├─middleware
    │ │ promiseMiddleware.js
    │ │
    │ └─reducers
    │ counter.js
    │ userInfo.js

    └─router #路由文件
    Bundle.js
    router.js

    快速搭建一个react项目:
    下载react脚手架 :

    C:\Users\Administrator> npm install -g creat-react-app
    C:\Users\Administrator> create-react-app my-app
    (也可进入指定文件目录下)

    在这里插入图片描述
    在这里插入图片描述
    这是简单的基础项目,他的核心:react / react-dom 两个框架
    特点:
    1.生成项目后,脚手架为了“优雅”,隐藏了所有的webpack相关的配置文件,此时查看myapp文件夹目录,会发现找不到任何webpack配置文件;这也就导致了,如果我们需要在webpack中安装一些自己的loder或者plugin变的很困难;
    2.create-react-app自动生成的webpack中集成了:eslint(代码检测)、url-loader(图片BASE64 [小于10000kb的图片])、babel-loader(ES6和JSX语法解析)、style-loader、css-loader(CSS代码解析)、HtmlWebpackPlugin(产出HTML插件)等内容
    3.仅仅是安装了react中最常用的 react / react-dom 组件,其余的并没有安装,所以在项目开发中,我们根据需要,可能还会安装:$ yarn add redux react-redux react-router-dom prop-types 等等

    完整的基础项目除了react / react-dom 两个框架,还包含
    redux
    react-redux
    react-router
    sass /less
    style-component
    history
    fetch
    webpack-dev-server

    项目所用到的插件及作用:
    redux: 帮你解决state变化和异步, state 可能包括服务器响应、缓存数据、本地生成尚未持久化到服务器的数据,也包括 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。
    1.单一数据源
    整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
    2.State 是只读的
    唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
    3.使用纯函数来执行修改
    为了描述 action 如何改变 state tree ,你需要编写 reducers。
    Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。

    react-redux : Redux 的作者封装了一个 React 专用的库 React-Redux,React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。
    1.UI 组件有以下几个特征。

    只负责 UI 的呈现,不带有任何业务逻辑
    没有状态(即不使用this.state这个变量)
    所有数据都由参数(this.props)提供
    不使用任何 Redux 的 API
    下面就是一个 UI 组件的例子。

    const Title =  value => <h1>{value}</h1>;
    

    因为不含有状态,UI 组件又称为"纯组件",即它纯函数一样,纯粹由参数决定它的值.

    2.容器组件的特征恰恰相反。
    负责管理数据和业务逻辑,不负责 UI 的呈现
    带有内部状态
    使用 Redux 的 API

    3.connect()
    React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来。

    import { connect } from 'react-redux'
    const VisibleTodoList = connect()(TodoList);
    

    上面代码中,TodoList是 UI 组件,VisibleTodoList就是由 React-Redux 通过connect方法自动生成的容器组件。

    但是,因为没有定义业务逻辑,上面这个容器组件毫无意义,只是 UI 组件的一个单纯的包装层。为了定义业务逻辑,需要给出下面两方面的信息。

    (1)输入逻辑:外部的数据(即state对象)如何转换为 UI 组件的参数

    (2)输出逻辑:用户发出的动作如何变为 Action 对象,从 UI 组件传出去。

    因此,connect方法的完整 API 如下。

    import { connect } from 'react-redux'
    const VisibleTodoList = connect(
      mapStateToProps,
      mapDispatchToProps
    )(TodoList)
    

    上面代码中,connect方法接受两个参数:mapStateToProps和mapDispatchToProps。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。

    React-Router
    基本用法
    1 路由定义(摘要: Router, Route)
    在一个路由系统中,我们需要定义好路由表。我们需要用Router组件包含我们的路由表,通过Route来声明单个的路由。同时,每个路由应该与它所属的组件一一对应

    render((
      <Router >//开始创建路由表
        <Route path="/" component={App}>//声明每一个路由
          <Route path="/about" component={About}/>
          <Route path="users" component={Users}>//每个路由
            <Route path=":id" component={User}/>//对应一个Component
          </Route>
        </Route>
      </Router>
    ), document.getElementById('example'))
    
    //其他定义路由表的方法
    
    import routes from './config/routes'
    render(<Router history={browserHistory} routes={routes}/>, document.getElementById('example'))
    

    2 路由嵌套(摘要: IndexRouter, this.props.children)
    如何实现路由的嵌套呢,与Angular路由一样,路由的嵌套需要两个条件。一、在创建路由表的时候就声明好嵌套的规则(ng中的stateProvider)view(nguiview)使IndexRoute(ngstateProvider);二、需要有一个view来安放所要嵌套的子页面(ng中的ui-view)。其中,我们还可以在嵌套层的首个路由声明中,使用IndexRoute来声明该路由下的默认路由(类似于ng中urlProvider.otherwise)

    //有嵌套与默认页面的路由表
    render((
      <Router >
        <Route path="/" component={App}>
          <IndexRoute component={Index}/>//设置默认页面
          <Route path="/about" component={About}/>
          <Route path="users" component={Users}>
            <IndexRoute component={UsersIndex}/>//设置默认页面
            <Route path=":id" component={User}/>
          </Route>
        </Route>
      </Router>
    ), document.getElementById('example'))
    
    //一个用于安放子页(子路由)的view
    class Users extends React.Component {
      render() {
        return (
          <div>
            <h2>Users</h2>
            {this.props.children}//此处相当于<ui-view>
          </div>
        )
      }
    }
    

    3 路由跳转(摘要: Link,to)
    我们需要一个Link组件帮助我们实现路由的的跳转

    <li><Link to="/users" >/users</Link></li>
    

    ,最终Link组件会被渲染为标签。to属性是我们所要跳转的路由pathname(类似于ng中的ui-sref / href)
    3 . 1 父子路由的参数穿透传递(摘要: to = {/xx/${xxx}/} )
    若父路由中包含不确定参数,而我们又想把该参数往下级传递,这时候我们需要这样子做
    //路由配置

      <Route path="user/:userID" component={User}>
        <Route path="tasks/:taskID" component={Task} />
        <Redirect from="todos/:taskID" to="tasks/:taskID" />
      </Route>
    //子级路由
    <li><Link to={`/user/${userID}/tasks/foo`} activeClassName="active">foo task</Link></li>
    

    3 . 2 带参数的路由跳转

    <li><Link      to={{ pathname: '/users/ryan', query: { foo: 'bar' } }} activeStyle={ACTIVE}>/users/ryan?foo=bar</Link></li>
    

    3 . 3 函数内跳转(摘要: this.context.router.push(’/’))
    this.context.router.push(’/’) ,注:这个写法会把跳转载入浏览器历史,若不想留下历史记录则可以 this.context.router.replace(’/’)

    带有动画效果的路由切换(摘要: ReactCSSTransitionGroup)
    当我们需要在路由切换时带有一定的动画效果时,我们便需要 react-addons-css-transition-group 这个插件了。使用ReactCSSTransitionGroup组件来包含我们需要呈现动画效果的view

    class App extends Component {
      render() {
        return (
          <div>
            <ul>
              <li><Link to="/page1">Page 1</Link></li>
              <li><Link to="/page2">Page 2</Link></li>
            </ul>
    
            <ReactCSSTransitionGroup component="div"  transitionName="example"  transitionEnterTimeout={500}  transitionLeaveTimeout={500}>
              {React.cloneElement(this.props.children, {
                key: this.props.location.pathname
              })}
            </ReactCSSTransitionGroup>
               //克隆所有子节点,单独的{this.props.children}没有动画效果
          </div>
        )
      }
    }
    

    路由的权限控制(摘要: onEnter、context.router)
    单页应用路由的权限控制的基本思路是:监听路由的改变,每当路由将要发生改变,我们就使用一个中间服务(该服务介于上一级路由和将要到达路由之间启动),来判断我们是否有进入这个路由的权限,有的话直接进入,没有的话就redirect。在React中,为某个路由进行权限监听的方式是onEnter ,该onEnter属性对应连着一个具有判断权限的中间服务。我们通过上一级路由来启动这个服务。假设我们需要从’/form’到’/page’之间做一个判断,在’/form’中填写特定字段后才能成功跳转,否则redirect到’/error’

    //form
    const Form = createClass({
      //省略部分代码
      submitAction(event) {
        event.preventDefault();
         //通过context传输数据
        //通过url的query字段传输数据
        //也可以通过制定其他服务来传输数据
        this.context.router.push({
          pathname: '/page',
          query: {
            qsparam: this.state.value
          }
        })
      },
      render() {
        return (
          <form onSubmit={this.submitAction}>
            //省略部分代码
            <button type="submit">Submit </button>
          </form>
        )
      }
    })
    
    //路由权限控制
    <Route path="page" component={Page} onEnter={requireCredentials}/>
    
    //权限控制的中间服务
    function requireCredentials(nextState, replace, next) {
      //获取传输过来的数据
      if (query.qsparam) {
        serverAuth(query.qsparam)
        .then(
          () => next(),//成功,通过next()成功跳转
          () => {
            replace('/error')//重定向
            next()
          }
        )
      } else {
        replace('/error')
        next()
      }
    }
    

    其中,onEnter所指向的函数是 type EnterHook = (nextState: RouterState, replace: RedirectFunction, callback?: Function) => any; 其中,nextState作为第一个参数,其所带的信息有如下:

    type RouterState = {
      location: Location;
      routes: Array<Route>;
      params: Params;
      components: Array<Component>;
    };
    

    其中,replace函数一旦被使用到,则在函数内部跳转到一个新url,返回时也要带上必要的信息,如下

    type RedirectFunction = (state: ?LocationState, pathname: Pathname | Path, query: ?Query) => void;
    

    路由离开确认(摘要: componentWillMount, this.context.router.setRouteLeaveHook) 
    若我们需要在路由切换,在离开当前页面的时候做一些确认工作,我们可以通过setRouteLeaveHook函数,为离开前执行一些操作

    //Component内部
      componentWillMount() {
        this.context.router.setRouteLeaveHook(
          this.props.route,
          this.routerWillLeave
        )
      }
      
      routerWillLeave() {
        if (xxx)
         //...
      },
    

    根据路由按需加载组件
    按需加载在单页应用中的好处不言而喻,按业务模块切分代码能使首次加载资源所需要的时间大大降低,能在一定程度上增强用户体验。但首先我们需要整理一下我们的项目结构(此demo是按路由切分的,另外还能按业务模块进行切分)
    项目结构
    在这里插入图片描述

    `路由表配置(app.js)
    在这里插入图片描述

    对应组件的加载配置(routes/hello/index.js和routes/test/index.js)
    在这里插入图片描述

    路由组件的属性(摘要: this.props)

    在这里插入图片描述

    路由Location属性

    type Location = {
      pathname: Pathname;
      search: QueryString;
      query: Query;
      state: LocationState;
      action: Action;
      key: LocationKey;
    };
    

    styled-component
    样式化组件,主要作用是它可以编写实际的CSS代码来设计组件样式,也不需要组件和样式之间的映射,即创建后就是一个正常的React 组件,并且可以附加样式给当前组件。
    几种常用的样式化组件方法:
    1.njectGlobal # 编写全局CSS的辅助方法。它不返回组件,而是直接将样式添加到样式表中
    这个跟我们平时在写html页面,会先把一些需要重置浏览器的样式加到页面上的做法类似,主要作用是:重置样式及书写全局可共用的样式

    import { injectGlobal } from 'styled-components';
    
    injectGlobal`
      @font-face {
        font-family: 'Operator Mono';
        src: url('../fonts/Operator-Mono.ttf');
      }
    
      body {
        margin: 0;
      }
    `;
    

    StyledComponent # 样式化组件声明方式:styled.tagname、styled(Component) 两种方式
    第一种直接通过styled点一个元素标签,将button元素转化成样式化组件
    第二种是重写样式化组件的部分样式,比如TomatoButton

    另外介绍两种方法
    .extend:创建一个新的StyledComponent并且继承它的规则
    如:TomatoButton继承了Button的样式规则,并使用一些与颜色相关的样式进行扩展(其实就是覆盖了被继承组件的某些样式)。

    const TomatoButton = Button.extend`    
      color: tomato;
      border-color: tomato;
    `;
    

    .withComponent 创建一个新的StyledComponent,并应用于其他的标签或组件,且使用相同的样式
    如:用标签替换标签,但还是使用相同的样式,相当于有的样式标签一样都有

    const Link = Button.withComponent('a')
    

    History
    history 一个管理js应用session会话历史的js库。它将不同环境(浏览器,node…)的变量统一成了一个简易的API来管理历史堆栈、导航、确认跳转、以及sessions间的持续状态。
    location
    你也可以使用 history对象来的方法来改变当前的location:
    一个 history 知道如何去监听浏览器地址栏的变化, 并解析这个 URL 转化为 location 对象, 然后 router 使用它匹配到路由,最后正确地渲染对应的组件。
    location对象包括:
    pathname 同window.location.pathname
    search 同window.location.search
    state 一个捆绑在这个地址上的object对象
    action PUSH, REPLACE, 或者 POP中的一个
    key 唯一ID

    常用的三种history
    hashHistory
    不需要服务器配置,在URL生成一个哈希来跟踪状态,通常在测试环境使用,也可以作为发布环境使用。

    ReactDOM.render((<Provider store={store}><Router history={hashHistory}><Route>//你的route</Route></Router></Provider>),document.getElementById('root'));
    import { Provider } from 'react-redux'
    import { Router, hashHistory} from 'react-router'
    

    browserHistory
    需要服务器端做配置,路径是真实的URL,是官方推荐首选。
    客户端配置

    ReactDOM.render((<Provider store={store}><Router history={browserHistory}><Route>//你的route</Route></Router></Provider>),document.getElementById('root'));
    import { Provider } from 'react-redux'
    import { Router, browserHistory } from 'react-router'
    

    服务端配置

    // 通常用于加载静态资源app.use(express.static(__dirname + '/public'))
    // 在你应用 JavaScript 文件中包含了一个 script 标签// 的 index.html 中处理任何一个 routeapp.get('*', function (request, response){response.sendFile(path.resolve(__dirname, 'public', 'index.html'))})
    app.listen(port)console.log("server started on port " + port)
    const express = require('express')
    const path = require('path')
    const port = process.env.PORT || 8080
    const app = express()
    

    为什么browserHistory需要服务端配置
    因为真实URL其实是指向服务器资源,比如我们经常使用的API接口,也是一个真实URL的资源路径,当通过真实URL访问网站的时候,第一次访问的是网站的域名,这个时候可以正常加载我们的网站js等文件,而用户手动刷新网页时,由于路径是指向服务器的真实路径,服务器端没有做路由配置,就会导致资源不存在,用户访问的资源不存在,返回给用户的是404错误。
    通过hashHistory来生成的URL就不会出现这样的问题,因为他不是指向真实的路由。

    Fetch
    被称为下一代Ajax技术,采用Promise方式来处理数据。
    Fetch的核心是HTTP Requests,Responses Headers和Bodypayload 的接口抽象,以及一个用于启动异步资源请求的全局fetch方法。

    上传JSON数据
    使用fetch()开机自检JSON编码的数据。

    var url = 'https://example.com/profile';
    var data = {username: 'example'};
    
    fetch(url, {
      method: 'POST', // or 'PUT'
      body: JSON.stringify(data), 
      headers: new Headers({
        'Content-Type': 'application/json'
      })
    }).then(res => res.json())
    .catch(error => console.error('Error:', error))
    .then(response => console.log('Success:', response));
    

    传文件
    可以使用 HTML

    <input type="file"/> 
    

    input 元素、FormData () 和fetch()来上载文件。

    var formData = new FormData();
    var fileField = document.querySelector("input[type='file']");
    
    formData.append('username', 'abc123');
    formData.append('avatar', fileField.files[0]);
    
    fetch('https://example.com/profile/avatar', {
      method: 'PUT',
      body: formData
    })
    .then(response => response.json())
    .catch(error => console.error('Error:', error))
    .then(response => console.log('Success:', response));
    

    提供您自己的请求对象
    您可以使用Request()构造函数创建请求对象,并将其作为fetch()方法参数传入,而不是将要请求的资源的路径传递到 fetch () 调用中。

    var myHeaders = new Headers();
    var myInit = { method: 'GET',
                   headers: myHeaders,
                   mode: 'cors',
                   cache: 'default' };
    
    var myRequest = new Request('flowers.jpg', myInit);
    
    fetch(myRequest).then(function(response) {
      return response.blob();
    }).then(function(myBlob) {
      var objectURL = URL.createObjectURL(myBlob);
      myImage.src = objectURL;
    });
    
    展开全文
  • 这是facebook团队的一个小项目,可以快速搭建一个react项目结构,容易上手,非常适合像我这样的初学者。 步骤 node.js环境: 首先你得安装这个,因为后续的npm命令是基于这个的,不赘述。 直接安装: 运行npm...

    说明

    这是facebook团队的一个小项目,可以快速搭建一个react的项目结构,容易上手,非常适合像我这样的初学者。

    步骤

    1. node.js环境:
      首先你得安装这个,因为后续的npm命令是基于这个的,不赘述。
    2. 直接安装:
      运行npm install -g create-react-app;
      为什么要-g这个我说一下我的情况,因为我在创建my-app的时候,一开始没有全局安装,导致npm create-react-app my-app的时候会提示要求在npm后加commond
    3. 创建my-app
      装完create-react-app之后可以运行:
    create-react-app my-app
    cd my-app
    npm start

    就会自动创建一个react项目了,他会自动打开
    这里说一下,my-app好像是规定的内容,你不可以改成xxx-app之类的其他名字,不然会报错并且不能生成目录。

    localhost:3000

    告诉你可以进一步的修改
    结构如下:

    my-app
    ├── README.md
    ├── node_modules
    ├── package.json
    ├── .gitignore
    ├── public
    │   └── favicon.ico
    │   └── index.html
    │   └── manifest.json
    └── src
        └── App.css
        └── App.js
        └── App.test.js
        └── index.css
        └── index.js
        └── logo.svg
        └── registerServiceWorker.js

    学习记录

    按照官方的github介绍,在app.js修改是保存直接在浏览器生效的,也就是热加载。
    试了一下,index.js也是保存生效的(其实之后npm run eject你会发现其实这也是依赖于webpack-dev-server webpack的)。
    官方说明:
    The page will automatically reload if you make changes to the code.
    You will see the build errors and lint warnings in the console.

    package.json的内容

    {
      "name": "my-app",
      "version": "0.1.0",
      "private": true,
      "dependencies": {
        "react": "^16.4.0",
        "react-dom": "^16.4.0",
        "react-scripts": "1.1.4"
      },
      "scripts": {
        "start": "react-scripts start",
        "build": "react-scripts build",
        "test": "react-scripts test --env=jsdom",
        "eject": "react-scripts eject"
      }
    }

    npm run start 跑的是react-scripts start,应该是开发模式,相应的npm run build对应的是生产模式,文件会放在build文件夹下,而且引用的都是压缩过的js(xx.min.js)。

    官方解释build:
    Builds the app for production to the build folder.
    It correctly bundles React in production mode and optimizes the build for the best performance.

    The build is minified and the filenames include the hashes.
    By default, it also includes a service worker so that your app loads from local cache on future visits.

    Your app is ready to be deployed.

    1.如果你也想和我一样习惯less/sass

    可以参考这篇文章,写的很全,并且试验过是可以的:
    https://segmentfault.com/a/1190000010162614

    展开全文
  • react 项目创建 首先安装脚手架工具,create-react-app npm install –g create-react-app 安装完成后,执行 ...react项目创建完成后的目录结构如图: 主要看 public 和 src 文件夹中的文件: p...

    react 项目创建

    首先安装脚手架工具,create-react-app

    npm install –g create-react-app
    

    安装完成后,执行

    create-react-app 项目路径

    执行完成后一个 react 项目就在指定的路径下创建

    react目录结构

    react项目创建完成后的目录结构如图:

    主要看 public 和 src 文件夹中的文件:

    public 文件夹是公共文件夹,与src最大的区别就是不会被webpack加工,用于存放一些静态文件。

    • manifest.json ,用于将web应用添加到设备主屏幕
    • robots.txt,爬虫爬取时应该遵守的规则
    • index.html,整个项目的入口文件
    • 其他文件都是图标

    src 文件夹是项目存放源文件的文件夹,项目的主要内容都会存放在里面。

    • App.js, 项目的主组件
    • App.css,App组件的样式
    • App.test.js,测试文件
    • index.css,整个项目的全局样式
    • index.js,JS的入口文件
    • serviceWorker.js,用于使项目可以离线运行

    再来详细看看 index.js  

    import React from 'react'; //导入react
    import ReactDOM from 'react-dom'; //导入react-dom
    import './index.css'; //导入index.css, 由这里引入全局样式
    import App from './App'; //导入App组件,即项目的主组件
    import * as serviceWorker from './serviceWorker'; //导入serviceWorker.js
    
    
    /*这一行的意思是在 id 为 root 的 dom 节点内渲染 App 组件,这个节点在 public 文件中的 index.html     
      中。
      可以这么理解,我们访问一个 react 项目时,就是访问了 index.html, 项目打包后 index.js 也会被  
      index.html 引入,index.js 在 root 这个 dom 节点渲染 App 这个 react 组件。
    */
    ReactDOM.render(<App />, document.getElementById('root'));
    
    
    // If you want your app to work offline and load faster, you can change
    // unregister() to register() below. Note this comes with some pitfalls.
    // Learn more about service workers: https://bit.ly/CRA-PWA
    serviceWorker.unregister(); 

     

    展开全文
  • 创建一个react项目

    2019-03-06 21:40:18
    一、创建一个react项目 一、全局安装react npm install create-react-app -g 二、 在桌面创建一个react项目 create-react-app ‘文件名’ 注: 文件名中不能含有大写字母【name can no longer contain capital ...
  • 最近接到需求使用React来搭建一个较为复杂的页面,所以先基于webpack来搭建了一个简易的初始结构项目地址:https://github.com/Kasol/React4Webpack.git 可以clone下来直接npm start运行。由于想从目录结构开始...
  • react脚手架 ...react提供了一个专门用于创建react项目的脚手架库: create-react-app 项目的整体技术架构为: react + webpack + es6 + babel + eslint 使用脚手架开发的项目的特点: 模块化(js是一...
  • 创建一个react项目有三种方式: 1.create-react-app 快速脚手架(简单,类似于 vue-cli 工具) 2.webpack一步一步构建 3. 第三方脚手架(generator-react-webpack,需要yeoman的支持) 这里主要介绍利用...
  • 首先,关于React的配置可以先参考之前的博客。(React代码规范建议阅读 Airbnb React代码规范,提高代码质量。)上面就是前端项目的目录结构。...每一个文件夹都是内部具体的组件。router是用来管理路由文件。 uti...
  • 在《React进阶(一):构建第一个React应用》博文中,介绍了React开发环境搭建及建立第一个React应用。本篇博文继续探讨React项目中合理的文件结构
  • 根据自己最近学习react 视频,下面谈谈如何去创建一个react项目 准备工作: ES6语法、html5、css3、JSX语法 node.js 这里我们根据react官网中给出的安装react项目步骤来创建一个react项目。 打开react官网,可以是...
  • React---项目结构分析

    2018-08-29 16:25:01
    ********************************************************* 到这项目已经创建完毕。 项目结构 ...public:(项目中的启动页面,react比较适合单页面项目应用开发,所以暂时只包含一个index....
  • 熟悉项目的结构,是开发的基本也是技术提升的一个重要途径,现总结下自己对react-native项目结构的分析与理解。正文项目结构init 的一个项目结构如图: tests:测试文件夹,执行命令 “npm test”会调用此文件夹,在...
  • 从头开始建立一个React App - 项目基本配置npm init 生成 package.json 文件.安装各种需要的依赖:npm install --save react - 安装React.npm install --save react-dom 安装React Dom,这个包是用来处理virtual DOM。...
  • 首先当你从git上面clone一个项目的时候怎么让项目跑起来, 首先看项目目录结构,找到README.md上面有项目运行的步骤, 如果没有可以看package.json文件,找到scripts 上面有dev 所以跑起来项目就使用npm run dev 有...
  • https://my.oschina.net/korabear/blog/1815170
  • React Native 结构目录 React Native结构目录  名称  描述 android目录 Android项目目录,包含了使用AndroidStudio开发项目的环境配置文件; ios目录 iOS项目目录,包含了XCode的环境 node_...
  • 本文从零开始,逐步讲解如何用react全家桶搭建一个完整的react项目。文中针对react、webpack、babel、react-route、redux、redux-saga的核心配置会加以讲解,通过这个项目,可以系统的了解react技术栈的主要知识,...
  • React Native项目结构

    2017-10-03 09:34:30
    、概念在react-native项目中包含了android和ios的完整的项目结构,可以通过android studio和xcode进行打开和运行,所以可以通过webstorm直接打开react-native的整个项目,然后通过android studio进行打开android...
  • react 项目 结构

    2019-04-04 11:52:46
    转载地址:https://blog.csdn.net/cinderella___/article/details/81603129 build // 编译目录 config // webpack配置 public // 公共文件 可以放一些第三方字体 样式库等 scripts ...
1 2 3 4 5 ... 20
收藏数 33,273
精华内容 13,309
关键字:

一个react项目结构