React实战:react+webpack+es6 实现简易todo app

React可谓如日中天,webpack也风声水起。React刚出来不久就浏览了一遍官网的文档,当时想这个新玩意挺“颠覆”,暂时保持观望好了。直到React Angular Vue三分天下的时候,还处于观望就不太妥了。再次看完官网的document,尝试实现一个todo应用来实践react。

如果说实现一个Blog是后端工程师入门的第一个应用,那么Todo可谓是前端开发者练手处女项目了。
下面就使用React实现一个简单的todo,实现基本的增删改的功能,其效果请访问react-todo

创建项目

初始化项目

前端发展太快,从打包工具框架/库都层出不穷,往往一个坑还没爬出来,就掉进了另外的坑。创建的todo主要采用node包的方式,使用webpack打包,具体的js代码使用ES6的语法。

初始化项目并创建一些基础文件,项目结构大概如下:

  ~  mkdir todos
  ~  cd todos && npm init
  todos  mkdir app app/components
  todos  touch index.html webpack.config.js
  todos  touch app/index.js app/components/app.js
  todos  tree
.
├── app
│   ├── components
│   │   └── app.js
│   └── index.js
├── index.html
├── package.json
└── webpack.config.js

2 directories, 5 files

安装依赖包

初始化项目之后,就需要安装所需要的库及其依赖。npm安装方式可以为开发环境或生产选择所安装的依赖。首先需要安装webpackwebpack-dev-server。这两个包需要全局安装,通过 npm install -g webpack webpack-dev-server,如果已经安装了,可以忽略。

随后将会安装编译ES6和JSX的编译工具babel。运行下面命令安装。react,react-dom是react的基础库,lodash则是一个函数库,用于使用ES6的一些新特性。

  todos  npm install --save react react-dom lodash

上述包是生产发布环境也需要的依赖,下面安装开发环境中使用的打包编译的loader包:

  todos  npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react react-hot-loader style-loader css-loader webpack webpack-dev-server

如果一切顺利,npm将会在package.json中显示已经安装好的包。迁移项目的时候,只需要npm install即可。

配置webpack

安装完所需要的依赖之后,配置webpack。webpack的配置比较简单。具体配置如下:

var webpack = require("webpack")
var path = require("path")

module.exports = {
 devtool: "inline-source-map",
 entry: [
  "webpack-dev-server/client?http://127.0.0.1:8080/",
  "webpack/hot/only-dev-server",
  "./app"
 ],

 output: {
  path: path.join(__dirname, "public"),
  filename: "bundle.js"
 },
 resolve: {
  modulesDirectories: ["node_modules", "app"],
  extensions: ["", ".js"]
 },
 module: {
  loaders: [
   {
    test: /\.jsx?$/,
    exclude: /node_modules/,
    loaders: ["react-hot", "babel?presets[]=react,presets[]=es2015"]
   },
   {
    test: /\.css?$/,
    exclude: /node_modules/,
    loaders: ["style", "css"]
   }
  ]
 },
 plugins: [
  new webpack.HotModuleReplacementPlugin(),
  new webpack.NoErrorsPlugin()
 ]
}

关于webpack的配置,并不是本篇的主题,想要了解更多的详细内容,可以查阅官网的文档。

Hello world

配置了webpack之后,编写html入口和js的入口文件,展示一下hello world啦。
编辑 index.html 文件

index.html

<!DOCTYPE html>
<html>
<head>
    <title>React Todos App</title>
</head>
<body>
    <div id="app" />
    <script src="bundle.js"></script>
</body>
</html>

入口的js文件

index.js

import React from 'react'
import {render} from 'react-dom'

render(<div>hello world</div> document.getElementById('app'))

运行 webpack-dev-server启动webpack服务器,使用浏览器打开 http://127.0.0.1:8080就能看见helloworld啦。这里webpack-dev-server是为了监测前端文件的变化,以便实时编译打包前端文件。

React的render方法,将自定义的的component挂载到html中的dom中(div#app)。

React 组件

前面通过react的render方法,创建了一个组件,下面创建更多的组件。编辑 components/app.js

import React from "react"

class App extends React.Component {

    render() {
        return (
         <div>
          <h1>React Todo App</h1>
         </div>
        )
    }
}

export default App

然后修改之前的入口文件index.js,将随后创建的App组件渲染到html中。

index.js

import React from "react"
import {render} from "react-dom"
import App from "components/app"

render(<App />, document.getElementById("app"))

刷新浏览器,就能看见新创建的App组件。

组件是React中的重要概念。对于软件界面,按钮,导航,表单这些可视化的界面都可以称之为组件,组件实现了逻辑和功能的封装。就像完积木一样,每个组件都是一个积木,多个积木可以合成一个大的积木,最终实现组件构成的用户界面。

React的组件都是用********大写********的拉丁字母开头,继承自React.Components类。render方法用于返回该组件的JSX代码。JSX是Facebook为了配合react定义的一套xml规范。与html及其相似,用于构建组件界面。需要注意,JSX的所有标签必须闭合。return之后必须返回一个组件元素,不能同时返回多个,如果有多个的,需要用div重新包装一次。

编写Todo

todo 列表

配置好基本环境之后,接下来将要完成一个完整的todo应用。这个小应用主要有两个大的组件,一个是用于创建todo条目,另外一个用于展示todo列表。下面先完成todo列表的组件。随着功能的增加,会经常编辑某个文件,文件内容也会变多,在此只会贴出变更的代码部分,不再贴出完整的文件内容,完整的文件内容可以参考源码

接下来在app.js 文件中定义数据,数据的获取方式很多,假设现在从本地获取数据。通过App这个组件逐渐把数据传递下去。定义了数据,需要借助React的state和props两个属性来实现数据传递。编辑app.js文件如下:

...
import TodoList from "components/todo-list"

const todos = [
 {
  task: 'Learning React',
  isCompleted: true
 },
 {
  task: 'Learning Jsx',
  isCompleted: false
 },
 {
  task: 'React in action',
  isCompleted: false
 }
]

class App extends React.Component {

 constructor(props){
  super(props)
  this.state = {
   todos: todos
  }
 }

    render() {
        return (
         <div>
          <h1>React Todo App</h1>
          <TodoList todos={this.state.todos}/>
         </div>
        )
    }
}

export default App;

propsstate 是react组件中重要的两个属性。它们本质都是js对象。props常用于存储一些不可变的组件属性,例如函数和方法,state则用于保留一些可变的数据结构,例如实际的数据和状态tag。

上述的代码定义了一些todos数据,然后把这些数据初始化给App组件。再通过TodoList组件的todo props传递给后者。也就是在TodoList内部,它的this.props.todos则为 App组件的this.state.todos。

TodoList是用于展示todo列表的组件。再创建一个文件。

  app  touch components/todo-list.js

编辑todo-list.js 如下:

todo-list.js
...
import _ from 'lodash'

class TodoList extends React.Component {

 renderItem(){
  return  _.map(this.props.todos, (todo, index) => {
   return (
    <tr key={index}>
     <td>{todo.task}</td><td>{todo.isCompleted ? 'done' : 'undo'}</td>
    </tr>
   )
  })
 }

    render() {
        return (
         <table>
          <thead>
           <tr>
            <th>Task</th><th>Action</th>
           </tr>
          </thead>
          <tbody>
           {this.renderItem()}
          </tbody>
         </table>
        )
    }
}

export default TodoList;

TodoList组件由两部分组成,table的head和body部分。body通过一个表格的行来展示todo的列表内容。通过lodash的map方法,可以迭代一个数组(this.props.todos)对象,然后把todo的列表拼装成表格返回。最后在tbody中调用函数renderItem。至此,大致的一个todo应用轮廓已经成形。接下来将要把TodoList这个组件更细化的拆分。主要拆分为head和item两个组件。

TodoHeader 组件

创建一个组件文件,用于表示todo应用的表头。

  app  touch components/todo-header.js

修改编辑的todo-list.js 文件

import TodoListHeader from "components/todo-list-header"

class TodoList extends React.Component {

 ...

    render() {
        return (
         <table>
          <TodoListHeader />
          <tbody>
           {this.renderItem()}
          </tbody>
         </table>
        )
    }
}

然后再编辑 todo-list-header.js

import React from 'react'

class TodoListHeader extends React.Component {

    render() {
        return  (
          <thead>
                <tr>
                    <th>Task</th>
                    <th>Action</th>
                </tr>
            </thead>
        )
    }
}

export default TodoListHeader

TodoListHeader 组件相当简单,只需要把thead的内容copy即可。

TodoListItem 组件

需要拆分列表组件稍微复杂一点点。因为针对todo的每一个列表,都有修改、删除的操作。因此这些事件可以封装成为一个单独的组件,即item是组件。编辑todo-list.js文件,修改render函数如下:

todo-list.js

...

import TodoListItem from "components/todo-list-item"

class TodoList extends React.Component {

 renderItem(){
  return  _.map(this.props.todos, (todo, index) => {
   return (
    <TodoListItem todo={todo} key={index}/>
   )
  })
 }

 ...
}

然后编辑todo-list-item.js文件,增加TodoListItem组件。和TodoListHeader组件类似,将之前renderItem中的jsx拷贝一份,通过this.props读取单条todo的数据即可。

由于 react使用了virtrul-dom来实现操作dom的性能。那么针对一些列表元素的dom,都需要给他们一个id,这个id可以使用 key={index} 来指定。

todo-list-item.js

import React from 'react'

class TodoListItem extends React.Component {
    render() {
        return (
            <tr key={this.props.index}>
                <td>{this.props.todo.task}</td><td>{this.props.todo.isCompleted ? 'done' : 'undo'}</td>
            </tr>
        )
    }
}

export default TodoListItem

Todo 创建

完成了todo列表的基本功能,下一步需要实现todo的创建功能。需要引入一个新的组建,TodoCreate。创建一个文件todo-create.js。编写如下内容:

todo-create.js

import React from 'react';

class TodoCreate extends React.Component {
    render() {
        return (
         <form>
          <input type="text" placeholder="What need I do?" ref="createInput" />
          <button>Create</button>
         </form>
        )
    }
}

export default TodoCreate

然后编辑app.js 文件,引入TodoCreate 组件。

app.js

import TodoCreate from "components/todo-create"

class App extends React.Component {
   ...
    render() {
        return (
            <div>
                <h1>React Todo App</h1>
                <TodoCreate />
                <TodoList todos={this.state.todos}/>
            </div>
        )
    }
}

React事件

TodoCreate组件实质是一个表单,一个表单域和提交按钮。button的点击事件会触发form的onsubmit事件。因此需要定义form的事件,同时给表单域提供了一个ref属性,用于react引用表单域对象。

todo-craete.js

class TodoCreate extends React.Component {
    render() {
        return (
            <form onSubmit={this.handleCreate.bind(this)}>
                <input type="text" placeholder="What need I do?" ref="createInput" />
                <button>Create</button>
            </form>
        )
    }

    handleCreate(event){
        event.preventDefault()

        const task = this.refs.createInput.value
        this.refs.createInput.value = ''
    }
}

给from增加了onSubmit事件函数handleCreate。handleCreate函数中先把form的默认事件除去,然后通过ref属性获取了表单的值。如果按照之前的编程习惯,此时这里可以处理增加todo的实际操作。可是如果这里增加了todo,那么如何渲染到todo列表的组件中呢?

实际上,TodoCreate和TodoList是同级的组件,他们通信的共同点是通过App组件,并且之前的数据源都是通过App组件往子组件传递。因此可以在App组件中定义函数用于操作todo的数据,子组件只需要在自己的事件函数中调用父组建函数实现数据通信。

React 的事件和原生的js事件很像,只是写法上使用驼峰式,并且还保证了浏览器的兼容性。这样的处理react随处可见,例如后面将会遇到的样式写法。ref是表单中常用的属性,用于引用一个dom元素。

编辑App.js文件

app.js

...

class App extends React.Component {
  ...

    render() {
        return (
            <div>
                <h1>React Todo App</h1>
                // 绑定createTask函数给子组件TodoCreate
                <TodoCreate createTask={this.createTask.bind(this)}/>
                <TodoList todos={this.state.todos}/>
            </div>
        )
    }
   // 增加createTask函数用于接受处理TodoCreate组件创建的task数据
    createTask(task){
        this.state.todos.push({
            task: task,
            isCompleted: false
        })
        this.setState({todos: this.state.todos})
    }
}

App组件中实现了createTask函数,该函数绑定到TodoCreate组件中,通过后者的handleCreate事件调用,并传递创建的task内容。createTask再把数据重新设置state,以便渲染整个数据变化的组件。之前的handleCreate将改下如下:

todo-create.js
class TodoCreate extends React.Component {
  ...

    handleCreate(event){
        event.preventDefault()
        const task = this.refs.createInput.value
        // 调用App组件的createTask函数用于操作todo数据
        this.props.createTask(task)
        this.refs.createInput.value = ''
    }
}

Todo 修改

完成了Todo的创建,应用的功能算是完成了一半,CURD操作,仅仅是完成了两步,还有最重要的修改和删除两个功能。

修改主要针对的是单条todo内容的数据进行操作,因此大部分逻辑都和TodoListItem组件有关,而基于前面的学习中,TodoCreate中的数据是需要借助App这个组件进行通信,同样TodoListItem中遇到数据的操作,也需要借助App的组件进行操作,比TodoCreate更复杂的情况是,TodoListItem的父组件确实TodoList,因此这个数据流的传递将会被TodoCreate多了一层组件。

action 操作

todo的action中的功能,对于todo列表,action将会提供编辑删除的功能,一旦点击了编辑,将会出现一个表单,同时action将会变成保存取消两个功能。一旦点取消,action将变成之前的样子。下面先实现这两组action的交互变化。

...

class TodoListItem extends React.Component {

    constructor(props){
        super(props)
        // 借助 isEditing state用于存储修改todo的状态
        this.state = {
            isEditing: false
        }
    }

    renderActionSection(){
        
        if(this.state.isEditing){
            return (
                <td>
                    <button>Save</button>
                    <button onClick={this.onCancel.bind(this)}>Cancel</button>
                </td>
            )
        }
        return (
            <td>
                <button onClick={this.onEditing.bind(this)}>Edit</button>
                <button>Delete</button>
            </td>
        )
        
    }

    render() {
        return (
            <tr key={this.props.index}>
                <td>{this.props.todo.task}</td>
                {this.renderActionSection()}
            </tr>
        )
    }

    onEditing(){
        this.setState({
            isEditing: true
        })

    }

    onCancel(){
        this.setState({
            isEditing: false
        })
    }

}

把动态变化的action内容抽出之后,点击编辑之后,除了action的按钮变化之外,还需要将task展示的地方变成一个form表单,以便实际修改task内容。因此在展示task内容的时候,需要根据当前的状态(是否是编辑)是否展示表单。

todo-list-item.js

class TodoListItem extends React.Component {
  ...

    renderTaskSection(){

        if (this.state.isEditing){
            return (
                <td>
                    <form>
                        <input type="text" defaultValue={this.props.todo.task} ref="editInput"/>
                    </form>
                </td>
            )
        }
        return <td>{this.props.todo.task}</td> 
    }

    render() {
        return (
            <tr key={this.props.index}>
                {this.renderTaskSection()}
                {this.renderActionSection()}
            </tr>
        )
    }
   ...

todo 编辑

点击编辑之后,会出现一个可编辑的表单,其中defaulValue属性比较重要,如果设置value,还需要针对表单的onchange事件进行监听,否则不会修改表单域的内容。

实现todo的编辑功能,通过表单提交来修改内容,我们之前也遇到了创建todo的时候需要提交表单,两者的思路类似,都是通过表单的事件,调用父组件的函数,然后更新todo的数据状态,最后重新render数据变化的组件。只不过这一次的函数还需要通过TodoList这个组件做一次数据流向的中继。

todo-list-item.js

class TodoListItem extends React.Component {
    renderActionSection(){ 
        if(this.state.isEditing){
            return (
                <td>
                    // 绑定save方法
                    <button onClick={this.onSave.bind(this)}>Save</button>
                    <button onClick={this.onCancel.bind(this)}>Cancel</button>
                </td>
            )
        }
       ...
        
    }

    renderTaskSection(){

        if (this.state.isEditing){
            return (
                <td>
                    // 绑定save方法
                    <form onSubmit={this.onSave.bind(this)}>
                        <input type="text" defaultValue={this.props.todo.task} ref="editInput"/>
                    </form>
                </td>
            )
        }
        return <td>{this.props.todo.task}</td> 
    }

    onSave(event){
        event.preventDefault()

        const oldTask = this.props.todo.task
        const newTask = this.refs.editInput.value

        // 调用父组件的方法
     this.props.saveTask(oldTask, newTask)
        this.setState({
            isEditing: false
        })

    }

下面实现saveTask方法,编辑 app.js文件

app.js

class App extends React.Component {

    ...

    render() {
        return (
            <div>
                <h1>React Todo App</h1>
                <TodoCreate createTask={this.createTask.bind(this)}/>
                <TodoList todos={this.state.todos}
                    // 将saveTask函数传递给子组件
                          saveTask={this.saveTask.bind(this)}/>
            </div>
        )
    }
    
  ...
  
    saveTask(oldTask, newTask){
        const foundTask = _.find(this.state.todos, todo => todo.task === oldTask)
        foundTask.task = newTask
        this.setState({todos: this.state.todos})
    }
}

完成了App组件中的saveTask函数定义,并传递给子组件,此时需要修改TodoList组件,并将这个函数方法继续传递给TodoListItem组件。

todo-list.js

class TodoList extends React.Component {

 renderItem(){
  return  _.map(this.props.todos, (todo, index) => {
   return (
      // 传递saveTask函数方法
    <TodoListItem todo={todo} key={index} saveTask={this.props.saveTask}/>
   )
  })
 }

    ...
}

通过TodoList组件的传递,编辑功能就可以实现了。下一步,将会实现将todo的状态进行改变,即完成与否的操作功能,点击todo条目,将变成删除线,表示已经完成;重新点击,将除去删除线,表示未完成。这是常见的前端toggle操作。修改TodoListItem组件

todo-list-item.js

class TodoListItem extends React.Component { 
  ... 
    renderTaskSection(){
   ...
   // 增加 taskStyle 和 完成状态的删除线
        if (!this.props.todo.isCompleted){
            return <td onClick={this.onToggle.bind(this)} style={taskStyle}>{this.props.todo.task}</td> 
        }

        return <td onClick={this.onToggle.bind(this)} style={taskStyle}><strike>{this.props.todo.task}</strike></td> 
    }
 
  ... 

    onToggle(){
        const currentTask = this.props.todo.task
        this.props.toggleTask(currentTask)
    }
}

在 renderTaskSection中,如果不是处于编辑状态,将对todo条目进行绑定一个onToggle的操作,以及将此时todo的状态用style颜色标注。style是Jsx中的组件的属性,本质上是一个js对象,js的对象就是把CSS的编写改写一下,和JSX组件属性一样,遇到连字符连接的属性,则改未驼峰式书写。

taskStyle = {
  color: this.props.todo.isCompleted ? 'green' : 'red',
  cursor: 'pointer'
}

下面来看onToggle方法,与onSave类似,调用的都是父级组件传递过来的方法操作todo数据state然后重新render组件。

React props特性

增加了编辑功能之后,还差一个删除,todo功能算是完成了。当然,现在还有两个小bug,稍后我们再fix。在此之前,针对TodoListItem组件的数据及其状态的修改,都是调用父级组件App定义的函数方法,其中通过TodoList传递,而每一次传递,都需要修改TodoList的代码,这一点实在太繁琐。为了解决这个问题,可以借助React和ES6的一些特性。下面修改TodoList组建。

todo-list.js

...

class TodoList extends React.Component {
    renderItem(){

        // return  _.map(this.props.todos, (todo, index) => {
        //     return (
        //         <TodoListItem todo={todo} key={index} saveTask={this.props.saveTask} toggleTask={this.props.toggleTask}/>
        //     )
        // })
   
   // 将 todo 对象直接传递
        return _.map(this.props.todos, (todo, index) => {
            return <TodoListItem key={index} {...todo} />
        })
    }
  ...
}

{...todo}写法可以把todo({task: task value, isCompleted: isCompleted value})对象传递给子组建,相当于给todo对象进行解包,等价于task=task valueisCompleted: isCompleted value。经过了这样处理,原TodoListItem组件中的task获取就不再是 this.props.todos.task ,而是变成了 this.props.task, 相应的isCompleted属性同理。即把 this.props.todos 替换成 this.props 即可。

使用 ... 封包和解包的功能是为了减少 props 属性的传递,之前繁琐的属性是各种事件,这些事件包含在 TodoList组件的 this.props 中,因此todo-list.js还需要再修改以便传递各种事件。

todo-list.js

class TodoList extends React.Component {

    renderItem(){
       // 除去 this.props 中的 todos属性,减少传递
        const props = _.omit(this.props, 'todos');
        return _.map(this.props.todos, (todo, index) => {
            return <TodoListItem key={index} {...todo} {...props} />
        })
    }

  ...
}

上述代码使用了lodash的omit方法,将todos属性除去,因为map的时候,会针对当前的item传递todo,因此,不需要把props中的todos传递了。通过是用...功能,省去了一大堆props的书写。{...props}取代了saveTask={this.props.saveTask} toggleTask={this.props.toggleTask}

删除&bugfix

删除功能

todo即将成形,完成删除功能和bugfix,再披上css样式,就大功告成了。实现删除功能很简单,基于前面的实践可知,再TodoListItem中绑定删除事件,然后调用App的删除方法即可,同时因为借助了...的解包方式,不需要再从TodoListItem中显式的传递这个函数方法啦。

todo-list-item.js

class TodoListItem extends React.Component {
    ... 

    renderActionSection(){
        ...
        return (
            <td>
                <button onClick={this.onEditing.bind(this)}>Edit</button>
                <button onClick={this.onDelete.bind(this)}>Delete</button>
            </td>
        )        
    }

    onDelete(){
        const currentTask = this.props.task
        this.props.deleteTask(currentTask)
    }
app.js

class App extends React.Component {

    render() {
        return (
            <div>
                ...
                <TodoList todos={this.state.todos}
                          deleteTask={this.deleteTask.bind(this)}
                          toggleTask={this.toggleTask.bind(this)}
                          saveTask={this.saveTask.bind(this)}/>
            </div>
        )
    }

    deleteTask(currentTask){
        _.remove(this.state.todos, todo => todo.task === currentTask)
        this.setState({todos: this.state.todos})
    }
    ...
}

验证

完成删除之后,可以尝试使用啦。在使用的时候,会发现,即使什么都不输入,也会增加一条空内容的task,同时,相同的task内容,在编辑修改的时候,总是修改成为第一条内容。产生bug的原因是在查找todo的时候,采用了task内容来匹配,而不是使用一个id之类的唯一标识。

解决的方法也很简单,在创建和编辑的时候,禁止发布空内容和相同的内容。这修要修改create和save两个函数方法,在其中增加一个验证的函数即可。


class TodoCreate extends React.Component {

    constructor(props){
        super(props)
        this.state = {
            error: null
        }
    }

    render() {
        return (
            <form onSubmit={this.handleCreate.bind(this)}>
                <input type="text" placeholder="What need I do?" ref="createInput" />
                <button>Create</button>
                {this.renderError()}
            </form>
        )
    }

    handleCreate(event){
        event.preventDefault()
        const task = this.refs.createInput.value

        const error = this.validateInput(task)
        if (error){
            this.setState({error: error})
            return 
        }
        this.props.createTask(task)
        this.refs.createInput.value = ''
    }

    validateInput(task){
        console.log(task)
        if (!task){
            return 'Please enter a task~'
        }else if (_.find(this.props.todos, todo => todo.task === task)){
            return 'Task already exsits!'
        }else{
            return ''
        }
    }

    renderError(){
        if (this.state.error){
            return <p>{this.state.error}</p>
        }
        return null
    }
}

因为用到了与现有的todo对比task内容,因此需要从App组件传给TodoCreate组件。

app.js

class App extends React.Component {

  ...
  
    render() {
        return (
            <div>
                <h1>React Todo App</h1>
                // 传递todos
                <TodoCreate todos={this.state.todos}
                    createTask={this.createTask.bind(this)}/>
                
                <TodoList todos={this.state.todos}
                          deleteTask={this.deleteTask.bind(this)}
                          toggleTask={this.toggleTask.bind(this)}
                          saveTask={this.saveTask.bind(this)}/>
            </div>
        )
    }
  ...

}

增加样式

与create类似,save的时候,也需要对task的内容做验证。这里就不再记录。具体实现看源码即可。源码的实现,把 validateTask方法抽出为公共的方法给TodoCreate 和 TodoListItem使用。

完成了基本功能之后,还需要给app披上一外衣,在这个看脸的时代,一副好皮囊至关重要。借助与webpack的模块打包功能,在react中使用css很简单,只需要把css文件当成模块import即可。例子中使用了siimple的css样式库。

index.js
...

import "siimple.css"
...

源码

总结

前端发展迅猛,之前jQuery一招鲜。随后backbone,angular等携带mvc等理想从后端杀入前端。一时前端战场硝烟弥漫,各种框架库层出不穷。最让人受不了的是一个工具还没掌握,就已经过时了。与其说前端发展快,私下觉得是因为前端缺少了太多东西,才需要工程师把别的端的理念在前端重新实现一遍。

不管怎么样,近年来逐渐偏向与react,angular,vue几个项目。在此不想比较它们孰优孰劣。就个人的感受而言,也许angular让你在写angular,vue也让你写vue,React却让你真正的在写js,而不是react。通过todo这个应用,大致可以明白React的基本用法和其核心概念。正如React创作的组件一样,这些函数库react,redux,webpack等同样也是一个个组件,如何搭配合理,发挥他们的生态功力。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,475评论 4 372
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,744评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,101评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,732评论 0 221
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,141评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,049评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,188评论 2 320
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,965评论 0 213
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,716评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,867评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,341评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,663评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,376评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,200评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,990评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,179评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,979评论 2 279

推荐阅读更多精彩内容

  • 最近看了一本关于学习方法论的书,强调了记笔记和坚持的重要性。这几天也刚好在学习React,所以我打算每天坚持一篇R...
    gaoer1938阅读 1,622评论 0 5
  • 翻译版本,原文请见,第一部分,第二部分 几周以前,我正在漫无目的的浏览Hacker News,读到一篇关于Redu...
    smartphp阅读 808评论 1 2
  • 作者:晓冬本文原创,转载请注明作者及出处 如今的 Web 前端已被 React、Vue 和 Angular 三分天...
    iKcamp阅读 801评论 0 2
  • 原教程内容详见精益 React 学习指南,这只是我在学习过程中的一些阅读笔记,个人觉得该教程讲解深入浅出,比目前大...
    leonaxiong阅读 2,771评论 1 18
  • 宁静的午后,平淡的心情,拿起一本好书,却发现自己根本读不下去,你是否也有这样的经历呢?我也是这样,甚至现在也市场出...
    崆山雨阅读 963评论 0 1