• React 入门-写个 TodoList 实例


    React 是一个用于构建用户界面的 JavaScript 库,主要特点有:

    • 声明式渲染:设计好数据和视图的关系,数据变化 React 自动渲染,不必亲自操作DOM
    • 组件化:页面切分成多个小部件,通过组装拼成整体页面,利于代码复用

    本文通过写个简单的 TodoList 实例,不求甚解,熟悉下 React 的开发过程。

    1. 安装 Node.js

    Node.js 是一个运行环境,类似 jdk,用以支持在服务端运行 JavaScript。

    您可以在这里下载安装包:

    http://nodejs.cn/download/
    

    以绿色版安装为例,将 node-v10.16.1-win-x64.zip 解压到 E:software 并命名为 node-v10.16.1

    在 Path 环境变量中增加两项:

    E:software
    ode-v10.16.1
    E:software
    ode-v10.16.1
    ode_global
    

    在 cmd 中使用 node -v 显示版本号,表示安装成功。

    Node.js 中有个 npm 软件包管理器,可以很方便的管理下载和使用第三方开源包,类似 maven,使用 npm -v 显示版本号,表示 npm 也没有问题。

    绿色版安装完成后一些必要的配置:

    npm config set prefix "E:software
    ode-v10.16.1
    ode_global"
    

    设置全局安装的模块存储路径

    npm config set cache "E:software
    ode-v10.16.1
    ode_cache"
    

    设置下载缓存的存储路径

    npm config set registry https://registry.npm.taobao.org`
    

    设置 npm 下载源为淘宝镜像

    简单使用:

    • npm install xxx: 安装到项目目录
    • npm install -g xxx 安装到全局目录
    • npm install -save xxx: 安装到项目目录,并在 package.json 中的 dependencies 节点记录依赖
    • npm install --save-dev xxx: 安装到项目目录,并在 package.json 中的 devDependencies 节点记录依赖

    2. 脚手架创建项目

    React 官方出的脚手架工具 create-react-app ,可以一键创建一个 Web 应用程序:

    cmd> npm install -g create-react-app
    cmd> cd E:
    cmd> create-react-app react-todoapp
    cmd> cd react-todoapp
    

    脚手架会在当前目录创建一个 react-todoapp 目录:

    react-todoapp
    ├── README.md
    ├── node_modules
    ├── package.json
    ├── package-lock.json
    ├── .gitignore
    ├── public
    │   ├── favicon.ico
    │   ├── index.html
    │   └── manifest.json
    └── src
        ├── App.css
        ├── App.js
        ├── App.test.js
        ├── index.css
        ├── index.js
        ├── logo.svg
        └── serviceWorker.js
        └── setupTests.js
    

    目录中主要的文件和文件夹说明:

    • README.md: 项目简介,支持 Markdown 语法
    • node_modules: 项目的依赖包,类似 Maven Repository
    • package.json: 配置项目依赖的第三方包,类似 pom.xml
    • package-lock.json: 锁定第三方包的版本号,保证 npm install 版本一致
    • public: 公开资源,网站路径,类似 nginx 的 html 目录
    • src: 核心组件代码文件

    为了便于开发,删除目录中不必要的文件,最终结构如下:

    react-todoapp
    ├── README.md
    ├── node_modules
    ├── package.json
    ├── package-lock.json
    ├── .gitignore
    ├── public
    │   └── index.html
    └── src
        ├── App.css
        ├── index.js
        ├── TodoApp.js
        ├── TodoItem.js
        └── TodoList.js
    

    接下来,设计与实现一个 TodoList 的例子,我们把所有代码过一下,敲一遍,先不管为什么,跑起来,最后再整理下知识点。

    3. 实例 TodoApp

    react-todoapp

    主要实现功能有:

    • 添加一个待办事项
    • 删除一个待办事项
    • 勾选复选框标记事项已完成

    如图所示,总共将页面拆分成了三个组件:TodoApp, TodoListTodoItem

    3.1 index.js 入口文件

    应该可以类比 java 的 main 方法,在 src 目录新建 index.js 内容如下:

    // 引入 React, ReactDOM
    import React from 'react';
    import ReactDOM from 'react-dom';
    // 引入 TodoApp 组件
    import TodoApp from './TodoApp';
    
    // 将渲染结果挂在到 root 节点,该节点在 index.html 中
    ReactDOM.render(
        <React.StrictMode>
            <TodoApp />
        </React.StrictMode>,
        document.getElementById('root')
    );
    

    先导入需要使用的组件(类),然后调用它们提供的方法和服务,有没有些许眼熟?

    3.2 TodoApp.js

    TodoApp 设计了页面整体布局,它包含全部数据以及操作这些数据的方法,是其他两个组件的父组件

    import React, { Component } from 'react';
    import TodoList from './TodoList';
    import './app.css';
    
    class TodoApp extends Component {
        constructor(props) { // 构造方法,props 应该是父类的一个成员变量
            super(props);
            this.state = { // 组件状态数据
                text: '',
                items:[{id: 1, status: 1, text: "去月球"},{id: 2, status: 0, text: "去火星"}]
            };
    
            // 设置 this 指向,默认 undefined
            this.handleChange   = this.handleChange.bind(this);
            this.handleAdd      = this.handleAdd.bind(this);
            this.handleComplete = this.handleComplete.bind(this);
            this.handleDelete   = this.handleDelete.bind(this);
        }
        // 渲染解析 jsx
        render() {
            return (
                <div className="todo">
                    <h3 className="text-center">Todos App</h3>
                    <TodoList 
                        items={this.state.items} 
                        handleComplete={this.handleComplete} 
                        handleDelete={this.handleDelete} />
    
                    <input className="input" type="text" placeholder="添加新任务" 
                        value={this.state.text} 
                        onChange={this.handleChange} />
    
                    <button className="btn-add" onClick={this.handleAdd}>添加</button>
                </div>
            );
        }
    
        handleChange(e) {
            this.setState({ text: e.target.value })
        }
    
        handleAdd(e) {
            e.preventDefault();
            if (this.state.text.length === 0) {
                return;
            }
    
            const newItem = {
                id: Date.now(),
                text: this.state.text,
                status: 0
            };
    
            this.setState({
                items: [...this.state.items, newItem],
                text: ''
            });
        }
    
        handleComplete(taskid) {
            // 临时变量,不直接修改原数据
            let items = this.state.items;
            let findItem = items.find(item => item.id === taskid);
            findItem.status = findItem.status === 0 ? 1 : 0;
    
            this.setState({
                items: items
            });
        }
        
        handleDelete(taskid) {
            let items = this.state.items;
            items = items.filter(item => item.id !== taskid);
            this.setState({
                items: items
            });
        }
    }
     
    export default TodoApp;
    

    3.3 TodoList.js

    TodoList 接收父组件 TodoApp 中的数组,并将其渲染成一个 ul 列表:

    import React, { Component } from 'react';
    import TodoItem from './TodoItem';
    
    class TodoList extends Component {
        render() {
            return (
                <ul className="list">
                    {
                        this.props.items.map((item)=>{
                            return (
                                <TodoItem key={item.id} 
                                    taskid={item.id}
                                    status = {item.status}
                                    text={item.text} 
                                    handleComplete={this.props.handleComplete} 
                                    handleDelete={this.props.handleDelete} />
                            )
                        })
                    }
                </ul>
            );
        }
    }
     
    export default TodoList;
    

    3.4 TodoItem.js

    在 TodoList 遍历数组时,把每一项元素交给 TodoItem 组件,它会渲染成一个 li 元素:

    import React, { Component } from 'react';
    
    class TodoItem extends Component {
        constructor(props) {
            super(props);
    
            this.taskComplete = this.taskComplete.bind(this);
            this.taskDelete   = this.taskDelete.bind(this);
        }
    
        render() {
            let isCompleted = this.props.status === 1;
            return (
                <li className={isCompleted?'complete':''}>
                    <input type="checkbox" 
                        checked={isCompleted}
                        onChange={this.taskComplete}/>
                    <span>{this.props.text}</span>
                    <button className="btn-del" onClick={this.taskDelete}>删除</button>
                </li>
            );
        }
    
        taskComplete() {
            this.props.handleComplete(this.props.taskid);
        }
    
        taskDelete() {
            this.props.handleDelete(this.props.taskid);
        }
    
        shouldComponentUpdate(nextProps, nextState) {
            if (nextProps.text !== this.props.text || nextProps.status !== this.props.status) {
                return true;
            } else {
                return false;
            }
        }
    }
     
    export default TodoItem;
    

    这几个文件写完之后,进入 react-todoapp 目录,cmd 运行 npm start,访问 http://localhost:3000 就能查看最终的结果了。

    4. 思考

    4.1 JSX

    TodoApp 组件在 render 方法渲染时,使用了一个既不是字符串也不是 HTML 的语法,它被称为 JSX,是 JavaScript 的语法扩展,使用它可以很方便的创建 DOM。

    JSX 看起来像是模板语言,但它具有 JavaScript 的全部功能:

    • 遇到 <> 就当作 HTML 解析
    • 遇到 {} 就当作 JavaScript 解析

    4.2 组件通信

    这里主要有两种通信情况:

    • 父组件向子组件通信
    • 子组件向父组件通信

    每个组件都有一个 props 对象,用以访问组件的属性,所以,父组件可以向子组件传递一组 props 供其使用,就像方法传参一样。

    子组件向父组件通信,可以利用回调函数:父组件将一个函数作为 props 传递给子组件,子组件调用该回调函数,便可向父组件通信。

    回调函数也是增删一组数据,那么为什么不直接把数据传给子组件,直接操作?这是因为 props只读的,不能修改,改了就会报错:

    TypeError: Cannot assign to read only property 'items' of object '#<Object>'
    

    这样设计是为了保证相同的输入,每次都输出相同的结果。

    4.3 组件状态

    组件分有状态无状态,比如 TodoApp 是有状态的,TodoList 和 TodoItem 是无状态的。这个状态 stateprops 类似,也是一组数据,但它是组件内部私有的,其他组件访问不了。

    所以,TodoApp 组件只有在自己内部才可以对 this.state.items 内部增删改,就算把它传给其他组件,也是只读的。

    在更新 state 时需要注意:

    • 不能直接修改 state:统一使用 setState() 更新
    • setState 是一个异步方法,可传递一个函数在执行结束后回调,setState({},()=>{..})
    • setState 会合并更新,就是可以只传递变更的部分

    组件的 state 可以随着用户交互而产生变化,但 props 一旦定义就不再发生改变。

    4.4 单向数据流

    子组件不能直接修改父组件的数据,数据只能由父组件传给子组件,更新只能通过回调,这个特性被称作单向数据流

    它保证了组件相同的输入,每次都是相同的输出。所有数据都在父组件,代码易于理解,方便维护。

    4.5 其他

    import React, { Component } from 'react'; 这是 ES6 解构赋值 的用法,解构赋值是对赋值运算符的扩展,可以将属性/值从对象/数组中取出,赋值给其他变量。

    这个导入就相当于:

    import React from 'react';
    const Component = React.Component;
    

    类比 Java 的话,可以这样理解,React 相当于包,Component 相当于包下的类,要使用都要先导入。

    5. 总结

    简单写了下自己的理解,仅供参考!还是要看官方文档:

    此次编写的 react-todoapp 源码地址:https://github.com/chuondev/react-todoapp

  • 相关阅读:
    【UVA12093】Protecting Zonk (树形DP)
    【UVA1579】俄罗斯套娃 Matryoshka (动态规划)
    【UVA1371】Period (二分+DP)
    【UVA1379】Pitcher Rotation (贪心+DP)
    【UVA1633】禁止的回文串(状压DP)
    【POJ3358】
    【POJ2773】Happy 2006 欧几里德
    【POJ1284】Primitive Roots 欧拉函数
    【POJ2478】Farey Seque
    【POJ3243】拓展BSGS(附hash版)
  • 原文地址:https://www.cnblogs.com/chuonye/p/14215897.html
Copyright © 2020-2023  润新知