React - 创建 React 项目、React 项目结构、React 简单案例、TodoList 案例

一、创建 React 项目

1、使用 create-react-app
  1. 全局安装
shell 复制代码
npm i -g create-react-app
  1. 创建项目

    create-react-app hello-react

  2. 启动项目

shell 复制代码
npm start
2、使用 Vite
  1. 全局安装
shell 复制代码
npm i -g pnpm
shell 复制代码
pnpm i -g vite
  1. 创建项目
shell 复制代码
pnpm create vite
  1. 启动项目
shell 复制代码
pnpm run dev

二、React 项目结构

文件 / 目录 说明
public 静态资源目录,包含 HTML 模板、图标等
src 源代码目录,包含 React 组件、样式等
.gitignore Git 忽略文件配置
package.json 项目配置文件,包含依赖、脚本等
package-lock.json 依赖版本锁定文件
README.md 项目说明文档
  1. public 目录
文件 / 目录 说明
favicon.icon 网站页签图标
index.html 主页面
logo192.png logo 图
logo512.png logo 图
manifest.json 应用加壳的配置文件
robots.txt 爬虫协议文件
  1. src 目录
文件 / 目录 说明
App.css App 组件的样式
App.js App 组件
App.test.js 用于给 App 做测试
index.css 样式
index.js 入口文件
logo.svg logo 图
reportWebVitals.js 页面性能分析文件,需要 web-vitals 库的支持
setupTests.js 组件单元测试的文件,需要 jest-dom 库的支持

三、React 简单案例

1、具体实现
  1. Hello 组件
js 复制代码
import React, { Component } from "react"; // 同时导入默认导出和命名导出
import hello from "./index.module.css";

export default class Hello extends Component {
    render() {
        return <h3 className={hello.title}>Hello React</h3>;
    }
}
css 复制代码
.title {
    background-color: orange;
}
  1. Welcome 组件
js 复制代码
import React, { Component } from "react";
import "./index.css";

export default class Welcome extends Component {
    render() {
        return <h3 className="title">Welcome</h3>;
    }
}
css 复制代码
.title {
    background-color: skyblue;
}
  1. App 组件
js 复制代码
import React, { Component } from "react";
import Hello from "./components/Hello";
import Welcome from "./components/Welcome";

export default class App extends Component {
    render() {
        return (
            <div>
                <Hello />
                <Welcome />
            </div>
        );
    }
}
  1. index.js
js 复制代码
import React from "react"; // 引入 React 核心库
import ReactDOM from "react-dom"; // 引入 ReactDOM
import App from "./App"; // 引入 App 组件

// 渲染 App 组件到页面
ReactDOM.render(<App />, document.getElementById("root"));
2、CSS Module
  1. index.css 是普通的、全局的样式表,在里面定义的类名,一旦被引入到项目中,就会成为全局的
js 复制代码
import "./index.css";
  1. index.module.css 是 CSS Module 样式表,当导入这个文件时,会得到一个对象,需要通过这个对象的属性来使用类名
js 复制代码
import hello from "./index.module.css";

四、TodoList 案例

  1. Header 组件
js 复制代码
import React, { Component } from "react";
import { nanoid } from "nanoid";
import PropTypes from "prop-types";
import "./index.css";

export default class Header extends Component {
    static propTypes = {
        addTodo: PropTypes.func.isRequired,
    };

    handleKeyUp = (e) => {
        if (e.keyCode !== 13) return;

        const value = e.target.value.trim();
        if (!value) return;

        const todoObj = { id: nanoid(), name: value, done: false };
        this.props.addTodo(todoObj);
        e.target.value = "";
    };

    render() {
        return (
            <div className="todo-header">
                <input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认" />
            </div>
        );
    }
}
css 复制代码
.todo-header input {
    width: 560px;
    height: 28px;
    font-size: 14px;
    border: 1px solid #ccc;
    border-radius: 4px;
    padding: 4px 7px;
}

.todo-header input:focus {
    outline: none;
    border-color: rgba(82, 168, 236, 0.8);
    box-shadow:
        inset 0 1px 1px rgba(0, 0, 0, 0.075),
        0 0 8px rgba(82, 168, 236, 0.6);
}
  1. Footer 组件
js 复制代码
import React, { Component } from "react";
import PropTypes from "prop-types";
import "./index.css";

export default class Footer extends Component {
    static propTypes = {
        todos: PropTypes.array.isRequired,
        checkAllTodo: PropTypes.func.isRequired,
        clearAllDone: PropTypes.func.isRequired,
    };

    handleCheckAll = (event) => {
        this.props.checkAllTodo(event.target.checked);
    };

    handleClearAllDone = () => {
        this.props.clearAllDone();
    };

    render() {
        const doneCount = this.props.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0); // 已完成个数
        const totalCount = this.props.todos.length; // 总个数
        return (
            <div className="todo-footer">
                <label>
                    <input type="checkbox" onChange={this.handleCheckAll} checked={doneCount === totalCount && totalCount !== 0 ? true : false} />
                </label>
                <span>
                    <span>已完成 {doneCount}</span> / 全部 {totalCount}
                </span>
                <button onClick={this.handleClearAllDone} className="btn btn-danger">
                    清除已完成任务
                </button>
            </div>
        );
    }
}
css 复制代码
.todo-footer {
    height: 40px;
    line-height: 40px;
    padding-left: 6px;
    margin-top: 5px;
}

.todo-footer label {
    display: inline-block;
    margin-right: 20px;
    cursor: pointer;
}

.todo-footer label input {
    position: relative;
    top: -1px;
    vertical-align: middle;
    margin-right: 5px;
}

.todo-footer button {
    float: right;
    margin-top: 5px;
}
  1. List 组件
js 复制代码
import React, { Component } from "react";
import PropTypes from "prop-types";
import Item from "../Item";
import "./index.css";

export default class List extends Component {
    static propTypes = {
        todos: PropTypes.array.isRequired,
        updateTodo: PropTypes.func.isRequired,
        deleteTodo: PropTypes.func.isRequired,
    };

    render() {
        const { todos, updateTodo, deleteTodo } = this.props;
        return (
            <ul className="todo-main">
                {todos.map((todo) => {
                    return <Item key={todo.id} {...todo} updateTodo={updateTodo} deleteTodo={deleteTodo} />;
                })}
            </ul>
        );
    }
}
css 复制代码
.todo-main {
    margin-left: 0px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding: 0px;
}

.todo-empty {
    height: 40px;
    line-height: 40px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding-left: 5px;
    margin-top: 10px;
}
  1. Item 组件
js 复制代码
import React, { Component } from "react";
import PropTypes from "prop-types";
import "./index.css";

export default class Item extends Component {
    state = { mouse: false }; // 标识鼠标移入、移出

    static propTypes = {
        id: PropTypes.string.isRequired,
        name: PropTypes.string.isRequired,
        done: PropTypes.bool.isRequired,
        updateTodo: PropTypes.func.isRequired,
        deleteTodo: PropTypes.func.isRequired,
    };

    handleMouse = (flag) => {
        return () => {
            this.setState({ mouse: flag });
        };
    };

    handleCheck = (id) => {
        return (event) => {
            this.props.updateTodo(id, event.target.checked);
        };
    };

    handleDelete = (id) => {
        if (window.confirm("确定删除吗?")) {
            this.props.deleteTodo(id);
        }
    };

    render() {
        const { id, name, done } = this.props;
        const { mouse } = this.state;
        return (
            <li style={{ backgroundColor: mouse ? "#ddd" : "white" }} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}>
                <label>
                    <input type="checkbox" checked={done} onChange={this.handleCheck(id)} />
                    <span>{name}</span>
                </label>
                <button onClick={() => this.handleDelete(id)} className="btn btn-danger" style={{ display: mouse ? "block" : "none" }}>
                    删除
                </button>
            </li>
        );
    }
}
css 复制代码
li {
    list-style: none;
    height: 36px;
    line-height: 36px;
    padding: 0 5px;
    border-bottom: 1px solid #ddd;
}

li label {
    float: left;
    cursor: pointer;
}

li label li input {
    vertical-align: middle;
    margin-right: 6px;
    position: relative;
    top: -1px;
}

li button {
    float: right;
    display: none;
    margin-top: 3px;
}

li:before {
    content: initial;
}

li:last-child {
    border-bottom: none;
}
  1. App 组件
js 复制代码
import React, { Component } from "react";
import Header from "./components/Header";
import List from "./components/List";
import Footer from "./components/Footer";
import "./App.css";

export default class App extends Component {
    state = {
        todos: [
            { id: "001", name: "吃饭", done: true },
            { id: "002", name: "睡觉", done: true },
            { id: "003", name: "打代码", done: false },
            { id: "004", name: "逛街", done: false },
        ],
    };

    addTodo = (todoObj) => {
        const { todos } = this.state;
        const newTodos = [todoObj, ...todos];
        this.setState({ todos: newTodos });
    };

    updateTodo = (id, done) => {
        const { todos } = this.state;
        const newTodos = todos.map((todoObj) => {
            if (todoObj.id === id) return { ...todoObj, done };
            else return todoObj;
        });
        this.setState({ todos: newTodos });
    };

    deleteTodo = (id) => {
        const { todos } = this.state;
        const newTodos = todos.filter((todoObj) => {
            return todoObj.id !== id;
        });
        this.setState({ todos: newTodos });
    };

    checkAllTodo = (done) => {
        const { todos } = this.state;
        const newTodos = todos.map((todoObj) => {
            return { ...todoObj, done };
        });
        this.setState({ todos: newTodos });
    };

    clearAllDone = () => {
        const { todos } = this.state;
        const newTodos = todos.filter((todoObj) => {
            return !todoObj.done;
        });
        this.setState({ todos: newTodos });
    };

    render() {
        const { todos } = this.state;
        return (
            <div className="todo-container">
                <div className="todo-wrap">
                    <Header addTodo={this.addTodo} />
                    <List todos={todos} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo} />
                    <Footer todos={todos} checkAllTodo={this.checkAllTodo} clearAllDone={this.clearAllDone} />
                </div>
            </div>
        );
    }
}
css 复制代码
body {
    background: #fff;
}

.btn {
    display: inline-block;
    padding: 4px 12px;
    margin-bottom: 0;
    font-size: 14px;
    line-height: 20px;
    text-align: center;
    vertical-align: middle;
    cursor: pointer;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.2),
        0 1px 2px rgba(0, 0, 0, 0.05);
    border-radius: 4px;
}

.btn-danger {
    color: #fff;
    background-color: #da4f49;
    border: 1px solid #bd362f;
}

.btn-danger:hover {
    color: #fff;
    background-color: #bd362f;
}

.btn:focus {
    outline: none;
}

.todo-container {
    width: 600px;
    margin: 0 auto;
}
.todo-container .todo-wrap {
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 5px;
}
  1. index.js
js 复制代码
import React from "react"; // 引入 React 核心库
import ReactDOM from "react-dom"; // 引入 ReactDOM
import App from "./App"; // 引入 App 组件

// 渲染 App 组件到页面
ReactDOM.render(<App />, document.getElementById("root"));
相关推荐
GISer_Jing1 小时前
React核心语法:组件化与声明式编程
前端·react.js·前端框架
DJ斯特拉1 小时前
文件上传(UUID防止重名文件&&阿里云实现云端上传&&MultipartFile接收前端文件)
前端
Alan Lu Pop1 小时前
React 表单提交关键词意外触发刷新
前端·javascript·react.js
掘金安东尼1 小时前
企业级Claw落地避坑指南:70%项目失败的真实原因
前端·面试·github
这儿有一堆花1 小时前
从技术标准到营销概念:深度解析 HTML5 与 H5 的演变与区别
前端·html·html5
SuperEugene1 小时前
Vue3 Pinia 状态管理规范:何时用 Pinia 何时用本地状态|状态管理与路由规范篇
开发语言·前端·javascript·vue.js·前端框架
Moment1 小时前
TypeScript 要换芯了,6.0 竟是旧编译器的最后一舞
前端·javascript·github
Cg136269159742 小时前
Element-入门
前端
萝卜白菜。2 小时前
TongWeb7.0配置tongweb-web.xml修改jsessionid名及其值的长度
xml·前端·word