第3天:React应用(基于React脚手架)

  • 以下代码示例的参考代码只提供源码文件夹/src目录下的文件。
  • 静态资源文件夹因为都一样,所以就不提供了,可以点击这里下载public目录
  • *代码的使用:根据步骤3.1.2执行完之后,替换掉相关文件夹即可。

3.1.1. react脚手架

  1. xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目
    1) 包含了所有需要的配置(语法检查、jsx编译、devServer…)
    2) 下载好了所有相关的依赖
    3) 可以直接运行一个简单效果

  2. react提供了一个用于创建react项目的脚手架库: create-react-app

  3. 项目的整体技术架构为: react + webpack + es6 + eslint

  4. 使用脚手架开发的项目的特点: 模块化, 组件化, 工程化

3.1.2. 创建项目并启动

  • 第一步,全局安装:npm i -g create-react-app
  • 第二步,切换到想创项目的目录,使用命令:create-react-app hello-react
  • 第三步,进入项目文件夹:cd hello-react
  • 第四步,启动项目:npm start

3.1.3. react脚手架项目结构

public ---- 静态资源文件夹
    favicon.icon ------ 网站页签图标
    index.html -------- 主页面
    logo192.png ------- logo图
    logo512.png ------- logo图
    manifest.json ----- 应用加壳的配置文件
    robots.txt -------- 爬虫协议文件
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库的支持)

3.1.4. 功能界面的组件化编码流程(通用)

  1. 拆分组件: 拆分界面,抽取组件
  2. 实现静态组件: 使用组件实现静态页面效果
  3. 实现动态组件
    1. 动态显示初始化数据
      1. 数据类型
      2. 数据名称
      3. 保存在哪个组件?
    2. 交互(从绑定事件监听开始)
——参考代代码——
  • src/App.jsx

    //创建“外壳”组件App
    import React,{Component} from 'react'
    import Hello from './components/Hello'
    import Welcome from './components/Welcome'
    //
    //创建并暴露App组件
    export default class App extends Component{
    render(){
        return (
            <div>
                <Hello/>
                <Welcome/>
            </div>
        )
    }
    }
  • src/index.js

    //引入react核心库
    import React from 'react'
    //引入ReactDOM
    import ReactDOM from 'react-dom'
    //引入App组件
    import App from './App'
    //
    //渲染App到页面
    ReactDOM.render(<App/>,document.getElementById('root'))
  • src/components/Hello/index.jsx

    import React,{Component} from 'react'
    import hello from './index.module.css'
    //
    export default class Hello extends Component{
    render(){
        return <h2 className={hello.title}>Hello,React!</h2>
    }
    }
  • src/components/Hello/index.module.css

    .title{
    background-color: orange;
    }
  • src/components/Welcome/index.jsx

    import React,{Component} from 'react'
    import './index.css'
    //
    export default class Welcome extends Component{
    render(){
        return <h2 className="title">Welcome</h2>
    }
    }
  • src/components/Welcome/index.css

    .title{
    background-color: skyblue;
    }

3.2. 组件的组合使用-TodoList

功能: 组件化实现此功能

  1. 显示所有todo列表
  2. 输入文本, 点击按钮显示到列表的首位, 并清除输入的文本
——参考代代码——
  • src/App.css

    /*base*/
    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;
    }
  • src/App.jsx

    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用于添加一个todo,接收的参数是todo对象
    addTodo = (todoObj)=>{
        //获取原todos
        const {todos} = this.state
        //追加一个todo
        const newTodos = [todoObj,...todos]
        //更新状态
        this.setState({todos:newTodos})
    }
    
    //updateTodo用于更新一个todo对象
    updateTodo = (id,done)=>{
        //获取状态中的todos
        const {todos} = this.state
        //匹配处理数据
        const newTodos = todos.map((todoObj)=>{
            if(todoObj.id === id) return {...todoObj,done}
            else return todoObj
        })
        this.setState({todos:newTodos})
    }
    
    //deleteTodo用于删除一个todo对象
    deleteTodo = (id)=>{
        //获取原来的todos
        const {todos} = this.state
        //删除指定id的todo对象
        const newTodos = todos.filter((todoObj)=>{
            return todoObj.id !== id
        })
        //更新状态
        this.setState({todos:newTodos})
    }
    
    //checkAllTodo用于全选
    checkAllTodo = (done)=>{
        //获取原来的todos
        const {todos} = this.state
        //加工数据
        const newTodos = todos.map((todoObj)=>{
            return {...todoObj,done}
        })
        //更新状态
        this.setState({todos:newTodos})
    }
    
    //clearAllDone用于清除所有已完成的
    clearAllDone = ()=>{
        //获取原来的todos
        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>
        )
    }
    }
  • src/index.js

    //引入react核心库
    import React from 'react'
    //引入ReactDOM
    import ReactDOM from 'react-dom'
    //引入App
    import App from './App'
    //
    ReactDOM.render(<App/>,document.getElementById('root'))
  • src/components/Footer/index.css

    /*footer*/
    .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;
    }
  • src/components/Footer/index.jsx

    import React, { Component } from 'react'
    import './index.css'
    //
    export default class Footer extends Component {
    
    //全选checkbox的回调
    handleCheckAll = (event)=>{
        this.props.checkAllTodo(event.target.checked)
    }
    
    //清除已完成任务的回调
    handleClearAllDone = ()=>{
        this.props.clearAllDone()
    }
    
    render() {
        const {todos} = this.props
        //已完成的个数
        const doneCount = todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0),0)
        //总数
        const total = todos.length
        return (
            <div className="todo-footer">
                <label>
                    <input type="checkbox" onChange={this.handleCheckAll} checked={doneCount === total && total !== 0 ? true : false}/>
                </label>
                <span>
                    <span>已完成{doneCount}</span> / 全部{total}
                </span>
                <button onClick={this.handleClearAllDone} className="btn btn-danger">清除已完成任务</button>
            </div>
        )
    }
    }
  • src/components/Header/index.css

    /*header*/
    .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);
    }
  • src/components/Header/index.jsx

    import React, { Component } from 'react'
    import PropTypes from 'prop-types'
    import {nanoid} from 'nanoid'
    import './index.css'
    //
    export default class Header extends Component {
    
    //对接收的props进行:类型、必要性的限制
    static propTypes = {
        addTodo:PropTypes.func.isRequired
    }
    
    //键盘事件的回调
    handleKeyUp = (event)=>{
        //解构赋值获取keyCode,target
        const {keyCode,target} = event
        //判断是否是回车按键
        if(keyCode !== 13) return
        //添加的todo名字不能为空
        if(target.value.trim() === ''){
            alert('输入不能为空')
            return
        }
        //准备好一个todo对象
        const todoObj = {id:nanoid(),name:target.value,done:false}
        //将todoObj传递给App
        this.props.addTodo(todoObj)
        //清空输入
        target.value = ''
    }
    
    render() {
        return (
            <div className="todo-header">
                <input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认"/>
            </div>
        )
    }
    }
  • src/components/Item/index.css

    /*item*/
    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;
    }
  • src/components/Item/index.jsx

    import React, { Component } from 'react'
    import './index.css'
    //
    export default class Item extends Component {
    
    state = {mouse:false} //标识鼠标移入、移出
    
    //鼠标移入、移出的回调
    handleMouse = (flag)=>{
        return ()=>{
            this.setState({mouse:flag})
        }
    }
    
    //勾选、取消勾选某一个todo的回调
    handleCheck = (id)=>{
        return (event)=>{
            this.props.updateTodo(id,event.target.checked)
        }
    }
    
    //删除一个todo的回调
    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>
        )
    }
    }
  • src/components/List/index.css

    /*main*/
    .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;
    }
  • src/components/List/index.jsx

    import React, { Component } from 'react'
    import PropTypes from 'prop-types'
    import Item from '../Item'
    import './index.css'
    //
    export default class List extends Component {
    
    //对接收的props进行:类型、必要性的限制
    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>
        )
    }
    }

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注