【转载】React入门-Todolist制作学习

我直接看的这个React TodoList的例子(非常好!):

http://www.reqianduan.com/2297.html

文中示例的代码访问路径:http://127.0.0.1:7080/

下面我自己写的部署的服务访问路径:http://127.0.0.1:7060/

服务器的配置使用了Nginx,部署和配置方法,可以参考以前关于Nginx的博客。

先在文中的github页面下载了包

https://github.com/YikaJ/react-todos

解压后,把package.json拷贝到代码目录。

{
  "name": "react-todos",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "react": "^0.13.3",
    "sass": "^0.5.0"
  },
  "devDependencies": {
    "babel-core": "^5.5.8",
    "babel-loader": "^5.1.4",
    "css-loader": "^0.14.5",
    "file-loader": "^0.8.4",
    "jsx-loader": "^0.13.2",
    "node-libs-browser": "^0.5.2",
    "node-sass": "^3.2.0",
    "sass-loader": "^1.0.2",
    "style-loader": "^0.12.3",
    "url-loader": "^0.5.6",
    "webpack": "^1.9.11"
  }
}

然后运行 npm install 安装依赖库。

安装好之后,把git目录里面的local db目录,放在node_modules目录里面。

需要配置webpack,把webpack.config.js拷贝到目录。

内容如下:

'use strict';
var path = require('path');

module.exports = {
    entry: [
        "./src/entry.js"
    ],
    output: {
        path: path.join(__dirname, 'out'),
        publicPath: './out/',
        filename: "bundle.js"
    },
    externals: {
        'react': 'React'
    },
    module: {
        loaders: [
            { test: /.js$/, loader: "jsx!babel", include: /src/},
            { test: /.css$/, loader: "style!css"},
            { test: /.scss$/, loader: "style!css!sass"},
            { test: /.svg$/, loader: "url?limit=8192"}
        ]
    }
};

在webstorm里面,可以直接打开一个目录,然后里面的各个文件都能导入。 

创建的react目录在: /Users/baidu/Documents/Data/Work/Code/Self/reactjs-todo (另外,上层目录中的reactjs-todo-demo是直接下载的示例程序)

在Webstorm里面打开后(并且按照示例创建了各个文件),列表如下:

其中一些值得注意的地方:

1. 在本机的Nginx上面配置访问。在 /usr/local/etc/nginx/servers (该目录被nginx主配置文件引用)里面创建一个react-todo.conf,内容如下:

server {
    listen       7060;
    server_name  localhost;
    index index.html;
    root /Users/baidu/Documents/Data/Work/Code/Self/reactjs-todo;
}

然后重启nginx,可以访问。如果有问题,可以查看日志。

brew services restart nginx

启动日志位置:

 /usr/local/var/log/nginx/error.log

2. 主文件是index.html,里面内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>React-Todos</title>
  <link href="http://cdn.bootcss.com/normalize/3.0.3/normalize.css" rel="stylesheet">
</head>
<body>
  <header>
    <h1>React-Todos</h1>
  </header>
  <div class="container">
    <div id="app"></div>
  </div>
<script src="http://cdn.bootcss.com/react/0.13.3/react.min.js"></script>
<script src="out/bundle.js"></script>
</body>
</html>

其中,"app" div是会被React进行替换的。

3. webpack配置文件里面包含如下内容:

entry: [
        "./src/entry.js"
    ],

所以需要先从entry.js开始,内容如下:

/**
 * Created by baidu on 16/10/29.
 */
'use strict';

require('./main.scss');

require('./components/App');


scss文件如下 (scss是为了生成css文件,参考webpack里面的配置{ test: /.scss$/, loader: "style!css!sass"}):

body{
    background: #f5f5f5;
}

header{
    h1{
        text-align: center;
        text-decoration: underline;
    }
}

.container{
     760px;
    margin: 0 auto;
}

.fl{
    float: left;
}
.fr{
    float: right;
}
.clearfix:after{
    content: "";
    display: table;
    height: 0;
    clear: both;
}

.panel{
    background: #fff;
    border: 1px solid #ddd;
    padding: 8px;
    box-shadow: 1px 1px 1px #000;

    .panel-header{
        input{
             90%;
            background: url("./svg/si-glyph-baby.svg") no-repeat;
            padding: 5px 5px;
            padding-left: 50px;
            font-size: 24px;
            border: none;
        }
        border-bottom: 3px solid #ddd;
    }
}

.todo-list{
    list-style: none;
    padding: 0;
    li{
        border-bottom: 1px solid #ddd;
        padding: 10px;
        font-size: 18px;

        input[type=checkbox]{
            margin-right: 10px;
        }

        button{
            font-size: 14px;
        }
    }
}

.todo-footer{
    margin-left: 10px;

    input[type=checkbox]{
        margin-right: 10px;
    }
}

App.js作为entry.js里面引用的文件,内容如下:

/**
 * Created by baidu on 16/10/29.
 */

import React from "react";
import LocalDb from "localDb";

import TodoHeader from "./TodoHeader";
import TodoMain from "./TodoMain";
import TodoFooter from "./TodoFooter";

class App extends React.Component {
    constructor() {
        super();
        this.db = new LocalDb("React-Todos");
        this.state = {
            todos: this.db.get("todos") || [],
            isAllChecked: false
        };
    }

    allChecked() {
        let isAllChecked = false;
        if (this.state.todos.every((todo)=> todo.isDone)) {
            isAllChecked = true;
        }
        this.setState({todos: this.state.todos, isAllChecked});
    }

    addTodo(todoItem) {
        this.state.todos.push(todoItem);
        this.allChecked();
        this.db.set('todos', this.state.todos);
    }

    changeTodoState(index, isDone, isChangeAll=false) {
        if (isChangeAll) {
            this.setState({
               todos: this.state.todos.map(
                   (todo)=>{
                       todo.isDone = isDone;
                       return todo;
                   }
               ),
               isAllChecked: isDone
            });
        } else {
            this.state.todos[index].isDone = isDone;
            this.allChecked();
        }
        this.db.set('todos', this.state.todos);
    }

    clearDone() {
        let todos = this.state.todos.filter(
            (todo)=>!todo.isDone
        );
        this.setState({
            todos: todos,
            isAllChecked: false
        });
        this.db.set('todos', todos);
    }

    deleteTodo(index) {
        this.state.todos.splice(index, 1);
        this.setState({todos: this.state.todos});
        this.db.set('todos', this.state.todos);
    }

    render() {
        var props = {
            todoCount: this.state.todos.length || 0,
            todoDoneCount: (this.state.todos && this.state.todos.filter((todo)=>todo.isDone)).length || 0
        };

        return (<div className = "panel">
            <TodoHeader addTodo={this.addTodo.bind(this)}/>
            <TodoMain deleteTodo={this.deleteTodo.bind(this)} todos={this.state.todos} changeTodoState={this.changeTodoState.bind(this)}/>
            <TodoFooter isAllChecked={this.state.isAllChecked} clearDone={this.clearDone.bind(this)} changeTodoState={this.changeTodoState.bind(this)} {...props}/>
        </div>)
    }

}

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

注意最后一句,是需要实际进行替换的。

如原文中所说,总的思路是,方法定义在主Component中,然后传递给子Component:

React的主流思想就是,所有的state状态和方法都是由父组件控制,然后通过props传递给子组件,形成一个单方向的数据链路,保持各组件的状态一致。

但是例子中的TodoItem的方法都是自行定义处理的,可能因为每个Item都独立吧。

App.js中有几点值得说明:

1. LocalDb是作者自己实现的存储。我看了一下源码,用到了 localStorage[localDb],实际上就是存在了Cookie里面。经过实验,只要删除Cookie,内容就不存在了。

2. “...props”这种语法很好用:

计算需要的数据后,通过props传递到子组件。如果细心的同学应该可以看到像这样的{...props},这就是我之前说过的spread操作符。如果我们没有用这个操作符,就要这样写:

<TodoFooter {...props} /> // spread操作符
<TodoFooter todoCount={props.todoCount} todoDoneCount={props.todoDoneCount} />

最佳的实践就是,当父组件传props给子组件,然后子组件要将props转发给孙子组件的时候,spread操作符简直让人愉悦!可以对一堆麻烦又丑又长的代码可以say goodbye了!

3. App.js主体的框架是:

// 判断是否所有任务的状态都完成,同步底部的全选框
allChecked()

// 添加一个任务,参数是一个todoItem的object
addTodo(todoItem)

// 改变任务的状态,index是第几个,isDone是状态,isChangeAll是控制全部状态的
changeTodoState(index, isDone, isChangeAll=false) // 参数默认位false

// 清空已完成
clearDone()

// 删除面板上第几个任务
deleteTodo(index)

// react用于渲染的函数
render(){
    <div className="panel">
        <TodoHeader />
        <TodoMain />
        <TodoFooter />
    </div>
}
我们可以从render函数看到整个组件的结构,可以看到其实结构非常简单,就是上中下。
上面的TodoHeader自然就是用来输入任务的地方,中间就是展示并操作todo
-list的,而底部就是显示数据并提供特殊操作。
这里还是要提醒一句,所有标签都必须闭合,即使是非结对的,也要用斜杠闭合上。

记得,最后要进行React.render的调用。最后我们将整个App渲染到DOM上即可。
React.render(<App/>, document.getElementById("app"));

TodoHeader, TodoMain, TodoFooter以及TodoItem的各个实现就不具体看了。总结而言:

我们通过父组件来控制状态,并通过props传递,来保证组件内的状态一致。

我们可以非常有效的维护我们的交互代码,因为我们一眼就知道,这个事件属于哪个组件管理

它的模型其实非常轻,只有View层,但是它带给我们全新的书写前端组件的方法是非常好的,我个人认为如果未来的站点交互性愈来愈多,React是很有可能代替jQuery成为必备的技能。

原文地址:https://www.cnblogs.com/charlesblc/p/6008930.html