React学习03-基于脚手架的React应用

news2024/12/29 12:49:06

初始化脚手架

使用 npx 创建

npx create-react-app 项目名

使用 npm install 创建

全局安装create-react-app包:

npm install -g create-react-app

创建脚手架:

create-react-app 项目名

npm 镜像

执行 create-react-app时,还会自动安装一些包,这个时候,默认使用的是npm,速度较慢,可以替换为国内源。

# 换源
npm config set registry https://registry.npm.taobao.org
# 查看修改的结果
npm config get registry

若已经安装cpm,则全局安装 create-react-app 时可以使用 cnpm ,但需要注意的是,在执行 create-react-app my-app时,还会自动安装一些包,这个时候,默认使用的还是npm

脚手架文件结构

public

  • favicon.ico
    偏爱图标,设置默认图标

  • index.html
    详见注释(英文注释为react自动生成)

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <!-- %PUBLIC_URL% 代表public文件夹的路径 -->
        <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
        <!-- 开启理想视口,用于移动端适配 -->
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <!-- 用于配置浏览器页签和地址栏颜色(仅支持Android手机浏览器) -->
        <meta name="theme-color" content="#000000" />
        <meta
          name="description"
          content="Web site created using create-react-app"
        />
        <!-- 用于指定网页添加到手机桌面后的图标(iPhone) -->
        <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
        <!--
          manifest.json provides metadata used when your web app is installed on a
          user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
          应用加壳时的配置文件
        -->
        <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
        <!--
          Notice the use of %PUBLIC_URL% in the tags above.
          It will be replaced with the URL of the `public` folder during the build.
          Only files inside the `public` folder can be referenced from the HTML.
    
          Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
          work correctly both with client-side routing and a non-root public URL.
          Learn how to configure a non-root public URL by running `npm run build`.
        -->
        <title>React App</title>
      </head>
      <body>
        <!-- 若浏览器不支持js则展示标签中的内容 -->
        <noscript>You need to enable JavaScript to run this app.</noscript>
        <div id="root"></div>
        <!--
          This HTML file is a template.
          If you open it directly in the browser, you will see an empty page.
    
          You can add webfonts, meta tags, or analytics to this file.
          The build step will place the bundled scripts into the <body> tag.
    
          To begin the development, run `npm start` or `yarn start`.
          To create a production bundle, use `npm run build` or `yarn build`.
        -->
      </body>
    </html>
    
  • robots.txt
    爬虫协议规则文件,限制网站爬虫抓取

src

  • App.js
    定义名为App的组件

  • App.css
    App组件的样式表

  • index.js
    入口文件

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import App from './App';
    import reportWebVitals from './reportWebVitals';
    
    ReactDOM.render(
      <React.StrictMode>
        <App />
      </React.StrictMode>,
      document.getElementById('root')
    );
    
    // If you want to start measuring performance in your app, pass a function
    // to log results (for example: reportWebVitals(console.log))
    // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
    reportWebVitals();
    

    App组件外包裹React.StrictMode标签可以对App组件代码进行检查,如:警告字符串类型的ref

  • index.css
    全局通用样式表,也可放在public文件夹中,在index.html中用link标签引入(不建议,破坏结构)

  • reportWebVitals.js
    记录页面性能

  • setupTests.js
    做组件测试(整体测试或单元测试)

一个简单的Hello案例

image-20220403223840528

index.js

// 引入React核心库
import React from 'react'
// 引入React-DOM
import ReactDOM from 'react-dom'
// 引入App组件
import App from './App' // .js后缀可省略

// 渲染App到页面
ReactDOM.render(<App/>, document.getElementById('root'))

App.js

// 创建“外壳”组件App
import React, {Component} from "react" // 这里的{Component}能够引用是因为React.Component被局部暴露
import Hello from "./Hello"
import Welcome from "./components/Welcome"

// 创建并暴露App组件
export default class App extends Component {
  render() {
    return(
      <div>
        <Hello/>
        <Welcome/>
      </div>
    )
  }
}

// 暴露App组件
// export default App

Hello.js

import React, {Component} from "react"

export default class Hello extends Component {
    render() {
        return(
            <h1>Hello React</h1>
        )
    }
}

Welcome.jsx

import React, {Component} from "react"
import './Welcome.css'

export default class Welcome extends Component {
    render() {
        return(
            <h1 className="wel">Welcome to line2!</h1>
        )
    }
}

Welcome.css

.wel {
    color: blueviolet;
}

效果

image-20220403224228464

需要注意的点

  1. js文件可以更改为jsx文件(index.js也是)

  2. import React, {Component} from "react" 里的{Component}能够引用是因为React.Component被局部暴露,即:未暴露不能这么写,可以写成:const {Component} = React,继承时不再写extends React.Component,直接写extends Component即可

  3. 组件文件夹中为每个子组件创建文件夹,可以有两种形式:

    • 文件位置:components/Welcome/Welcome.js,引用方式:import Welcome from "./components/Welcome/Welcome"
    • 文件位置:components/Welcome/index.js,引用方式:import Welcome from "./components/Welcome"
  4. 样式的模块化:将Welcome.css更名为Welcome.module.css,随后在对应组件中引入import welcome from './index.module.css'组件完整代码如下:

    import React, {Component} from "react"
    import welcome from './index.module.css'
    
    export default class Welcome extends Component {
        render() {
            return(
                <h1 className={welcome.wel}>Welcome to line2!</h1>
            )
        }
    }
    

功能界面的组件化编码流程

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

TodoList案例

文件结构

image-20220404145925526

静态页面准备

index.js

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'

ReactDOM.render(<App/>, document.getElementById('root'))

App.jsx

import React, { Component } from 'react'

import Header from './components/Header/Header'
import List from './components/List/List'
import Footer from './components/Footer/Footer'

import './App.css'

export default class App extends Component {
  render() {
    return (
        <div className="todo-container">
            <div className="todo-wrap">
                <Header />
                <List />
                <Footer />
            </div>
        </div>
    )
  }
}

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;
  }

Header.jsx

import React, { Component } from 'react'

import './Header.css'

export default class Header extends Component {
  render() {
    return (
        <div className="todo-header">
            <input type="text" placeholder="请输入你的任务名称,按回车键确认"/>
        </div>
    )
  }
}

Header.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);
}

List.jsx

import React, { Component } from 'react'

import Item from '../Item/Item'

import './List.css'

export default class List extends Component {
  render() {
    return (
        <ul className="todo-main">
            <Item />
            <Item />
        </ul>
    )
  }
}

List.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;
  }

Item.jsx

import React, { Component } from 'react'

import './Item.css'

export default class Item extends Component {
  render() {
    return (
        <li>
        <label>
            <input type="checkbox"/>
            <span>xxxxx</span>
        </label>
        <button className="btn btn-danger" style={{display: 'none'}}>删除</button>
        </li>
    )
  }
}

Item.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;
  }

Footer.jsx

import React, { Component } from 'react'

import './Footer.css'

export default class Footer extends Component {
  render() {
    return (
        <div className="todo-footer">
            <label>
                <input type="checkbox"/>
            </label>
            <span>
                <span>已完成0</span> / 全部2
            </span>
            <button className="btn btn-danger">清除已完成任务</button>
        </div>
    )
  }
}

Footer.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;
  }

动态初始化列表

目前没有学消息订阅与发布,兄弟组件间无法通信,所以将状态放在App组件中。

App.jsx

// 初始化状态
state = {todoList:[
    {id: 1, name: '吃饭', done: true},
    {id: 2, name: '睡觉', done: true},
    {id: 3, name: '写代码', done: false}
]}

render() {
const {todoList} = this.state
return (
    <div className="todo-container">
        <div className="todo-wrap">
            <Header/>
            <List todoList={todoList}/>
            <Footer/>
        </div>
    </div>
)

List组件接收后使用展开运算符传递给Item组件

List.jsx

render() {
    const {todoList} = this.props
    return (
        <ul className="todo-main">
            {
                todoList.map((todo) => { 
                    return <Item key={todo.id} {...todo}/>
                })
            }
        </ul>
    )
}

Item组件接收props并展示

Item.jsx

render() {
    const {id, name, done} = this.props
    return (
        <li>
        <label>
            <input type="checkbox" defaultChecked={done}/> // 不能使用checked,会将选定状态写死,defaultChecked可能产生bug
            <span>{name}</span>
        </label>
        <button className="btn btn-danger" style={{display: 'none'}}>删除</button>
        </li>
    )
}

效果如下:

image-20220404161249053

实现添加一个Todo

Header组件中为input添加onKeyUp事件

Header.jsx

export default class Header extends Component {
    handleKeyUp = (event) => {
        const {target, key} = event // 不建议使用keyCode
        if (key !== 'Enter') {
            return
        } 
        console.log(target.value)
    }
    render() {
        return (
            <div className="todo-header">
                <input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认"/>
            </div>
        )
    }
}

GIF 2022-4-4 16-44-42

App组件向Header组件传递addTodo函数

App.jsx

// addTodo用于添加一个todo,接收的参数是todo对象
addTodo = (todoObj) => {
    // 获取原todoList
    const {todoList} = this.state
    // 追加一个todo
    const newTodos = [todoObj, ...todoList]
    // 更新状态
    this.setState({todoList: newTodos})
}

render() {
const {todoList} = this.state
return (
    <div className="todo-container">
        <div className="todo-wrap">
            <Header addTodo={this.addTodo}/>
            <List todoList={todoList}/>
            <Footer/>
        </div>
    </div>
)

Header组件onKeyUp对输入数据进行处理并传回App组件

Header.jsx

handleKeyUp = (event) => {
    // 解构赋值获取target, key
    const {target, key} = event
    // 判断是否是回车
    if (key !== 'Enter') {
        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 = ''
}

nanoid

使用nanoid或uuid生成唯一id值

执行npm i nanoidyarn add nanoid安装依赖,使用时执行引入语句import { nanoid } from 'nanoid',调用nanoid()函数即可

效果

GIF 2022-4-4 17-41-13

鼠标悬浮效果

Item组件设置state,并添加鼠标移入移出监听

Item.jsx

export default class Item extends Component {

    state = {mouse: false}

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

    render() {
        const {id, name, done} = this.props
        const {mouse} = this.state
        return (
            <li style={{backgroundColor: mouse ? '#ddd' : '#fff'}} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}>
                <label>
                    <input type="checkbox" defaultChecked={done}/>
                    <span>{name}</span>
                </label>
                <button className="btn btn-danger" style={{display: mouse ? 'block' : 'none'}}>删除</button>
            </li>
        )
    }
}

GIF 2022-4-4 22-14-16

复选框更新状态

App组件通过List组件向Item组件传递updateTodo函数

App.jsx

// updateTodo用于更新一个todo对象
updateTodo = (id, done) => {
    // 获取状态中的todoList
    const {todoList} = this.state
    // 加工数据(匹配处理)
    const newTodos = todoList.map((todoObj) => {
        if (todoObj.id === id) {
            return {...todoObj, done} // 简写形式,{...todoObj, done: done}
        } else {
            return todoObj
        }
    })
    this.setState({todoList: newTodos})
}

render() {
    const {todoList} = this.state
    return (
        <div className="todo-container">
            <div className="todo-wrap">
                <Header addTodo={this.addTodo}/>
                <List todoList={todoList} updateTodo={this.updateTodo}/>
                <Footer/>
            </div>
        </div>
    )

List.jsx

return <Item key={todo.id} {...todo} updateTodo={updateTodo}/>

Item.jsx

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

render() {
    const {id, name, done} = this.props
    const {mouse} = this.state
    return (
        <li style={{backgroundColor: mouse ? '#ddd' : '#fff'}} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}>
            <label>
                <input type="checkbox" defaultChecked={done} onChange={this.handleChange(id)}/>
                <span>{name}</span>
            </label>
            <button className="btn btn-danger" style={{display: mouse ? 'block' : 'none'}}>删除</button>
        </li>
    )
}

GIF 2022-4-4 22-43-51

对props进行限制

引入prop-types库

import PropTypes from 'prop-types'

Header.jsx

// 对接收的props进行类型、必要性的限制
static propTypes = {
    addTodo: PropTypes.func.isRequired
}

List.jsx

// 对接收的props进行类型、必要性的限制
static propTypes = {
    todoList: PropTypes.array.isRequired,
    updateTodo: PropTypes.func.isRequired
}

实现删除一个Todo

addTodoupdateTodo实现同理,App通过List向Item传递deleteTodo

App.jsx

// deleteTodo用于删除一个todo对象
deleteTodo = (id) => {
    // 获取原todoList
    const {todoList} = this.state
    // 删除指定id的todo对象
    const newTodos = todoList.filter((todoObj) => { 
        return todoObj.id !== id
    })
    // 更新状态
    this.setState({todoList: newTodos})
}

render() {
    const {todoList} = this.state
    return (
        <div className="todo-container">
            <div className="todo-wrap">
                <Header addTodo={this.addTodo}/>
                <List todoList={todoList} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo}/>
                <Footer/>
            </div>
        </div>
    )
}

List.jsx

export default class List extends Component {

    // 对接收的props进行类型、必要性的限制
    static propTypes = {
        todoList: PropTypes.array.isRequired,
        updateTodo: PropTypes.func.isRequired,
        deleteTodo: PropTypes.func.isRequired
    }

    render() {
        const {todoList, updateTodo, deleteTodo} = this.props
        return (
            <ul className="todo-main">
                {
                    todoList.map((todo) => { 
                        return <Item key={todo.id} {...todo} updateTodo={updateTodo} deleteTodo={deleteTodo}/>
                    })
                }
            </ul>
        )
    }
}

Item.jsx

// 删除一个todo的回调
handleDelete = (id) => {
    if (window.confirm('确定删除吗?')) { // 此处要加window,否则会报错Unexpected use of 'confirm'.
        this.props.deleteTodo(id)
    }
}

render() {
    const {id, name, done} = this.props
    const {mouse} = this.state
    return (
        <li style={{backgroundColor: mouse ? '#ddd' : '#fff'}} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}>
            <label>
                <input type="checkbox" defaultChecked={done} onChange={this.handleChange(id)}/>
                <span>{name}</span>
            </label>
            <button onClick={() => this.handleDelete(id) } className="btn btn-danger" style={{display: mouse ? 'block' : 'none'}}>删除</button>
        </li>
    )
}

JavaScript中,关于消息提示框的方法有三个:

  • alert(message)方法用于显示带有一条指定消息和一个 OK 按钮的警告框。
  • confirm(message)方法用于显示一个带有指定消息和 OK 及取消按钮的对话框。如果用户点击确定按钮,则 confirm() 返回 true。如果点击取消按钮,则 confirm() 返回 false。
  • prompt(text,defaultText)方法用于显示可提示用户进行输入的对话框。如果用户单击提示框的取消按钮,则返回 null。如果用户单击确认按钮,则返回输入字段当前显示的文本。

GIF 2022-4-5 10-10-52

实现footer的功能

重要

defaultChecked修改为checked

defaultChecked指定复选框的初始状态,一旦指定后无法修改

已完成/全部 + 全选

  • 已完成
    • App组件向Footer组件传递todoList
    • Footer组件计算已完成数目和总数
  • 全选
    • App组件向Footer组件传递checkAllTodo
    • Footer组件完成全选checkbox的回调

App.jsx

// checkAllTodo用于todo对象的全选
checkAllTodo = (done) => {
    // 获取原todoList
    const {todoList} = this.state
    // 加工数据
    const newTodos = todoList.map((todoObj) => { return {...todoObj, done} })
    // 更新状态
    this.setState({todoList: newTodos})
}

render() {
    const {todoList} = this.state
    return (
        <div className="todo-container">
            <div className="todo-wrap">
                <Header addTodo={this.addTodo}/>
                <List todoList={todoList} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo}/>
                <Footer todoList={todoList} checkAllTodo={this.checkAllTodo}/>
            </div>
        </div>
    )
}

Footer.jsx

export default class Footer extends Component {

    // 全选checkbox的回调
    handleChangeAll = (event) => {
        this.props.checkAllTodo(event.target.checked)
    }

    render() {

        const {todoList} = this.props

        // 已完成的个数
        const doneCount = todoList.reduce((pre, currentTodo) => pre + (currentTodo.done ? 1 : 0), 0) // pre上一次的返回值
        // console.log('@', doneCount)

        // 总数
        const total = todoList.length

        return (
            <div className="todo-footer">
                <label>
                    <input type="checkbox" onChange={this.handleChangeAll} checked={doneCount === total && total !== 0}/>
                </label>
                <span>
                    <span>已完成{doneCount}</span> / 全部{total}
                </span>
                <button className="btn btn-danger">清除已完成任务</button>
            </div>
        )
    }
}

清除全部已完成

App组件向Footer组件传递clearAllDoneTodo

App.jsx

// clearAllDoneTodo用于清除所有已完成任务
clearAllDoneTodo = () => {
    // 获取原todoList
    const {todoList} = this.state
    // 过滤数据
    const newTodos = todoList.filter((todoObj) => { return !todoObj.done })
    // 更新状态
    this.setState({todoList: newTodos})
}

render() {
    const {todoList} = this.state
    return (
        <div className="todo-container">
            <div className="todo-wrap">
                <Header addTodo={this.addTodo}/>
                <List todoList={todoList} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo}/>
                <Footer todoList={todoList} checkAllTodo={this.checkAllTodo} clearAllDoneTodo={this.clearAllDoneTodo}/>
            </div>
        </div>
    )
}

Footer.jsx

this.handleClearAll = () => {
    this.props.clearAllDoneTodo()
}

return (
    <div className="todo-footer">
        <label>
            <input type="checkbox" onChange={this.handleChangeAll} checked={doneCount === total && total !== 0}/>
        </label>
        <span>
            <span>已完成{doneCount}</span> / 全部{total}
        </span>
        <button onClick={this.handleClearAll} className="btn btn-danger">清除已完成任务</button>
    </div>
)

完整逻辑代码与最终效果

App.jsx

import React, { Component } from 'react'

import Header from './components/Header/Header'
import List from './components/List/List'
import Footer from './components/Footer/Footer'

import './App.css'

export default class App extends Component {

    // 初始化状态
    state = {todoList:[
        {id: '1', name: '吃饭', done: true},
        {id: '2', name: '睡觉', done: true},
        {id: '3', name: '写代码', done: false}
    ]}

    // addTodo用于添加一个todo,接收的参数是todo对象
    addTodo = (todoObj) => {
        // 获取原todoList
        const {todoList} = this.state
        // 追加一个todo
        const newTodos = [todoObj, ...todoList]
        // 更新状态
        this.setState({todoList: newTodos})
    }

    // updateTodo用于更新一个todo对象
    updateTodo = (id, done) => {
        // 获取状态中的todoList
        const {todoList} = this.state
        // 加工数据(匹配处理)
        const newTodos = todoList.map((todoObj) => {
            if (todoObj.id === id) {
                return {...todoObj, done} // 简写形式,{...todoObj, done: done}
            } else {
                return todoObj
            }
        })
        this.setState({todoList: newTodos})
    }

    // deleteTodo用于删除一个todo对象
    deleteTodo = (id) => {
        // 获取原todoList
        const {todoList} = this.state
        // 删除指定id的todo对象
        const newTodos = todoList.filter((todoObj) => { 
            return todoObj.id !== id
        })
        // 更新状态
        this.setState({todoList: newTodos})
    }

    // checkAllTodo用于todo对象的全选
    checkAllTodo = (done) => {
        // 获取原todoList
        const {todoList} = this.state
        // 加工数据
        const newTodos = todoList.map((todoObj) => { return {...todoObj, done} })
        // 更新状态
        this.setState({todoList: newTodos})
    }

    // clearAllDoneTodo用于清除所有已完成任务
    clearAllDoneTodo = () => {
        // 获取原todoList
        const {todoList} = this.state
        // 过滤数据
        const newTodos = todoList.filter((todoObj) => { return !todoObj.done })
        // 更新状态
        this.setState({todoList: newTodos})
    }

    render() {
        const {todoList} = this.state
        return (
            <div className="todo-container">
                <div className="todo-wrap">
                    <Header addTodo={this.addTodo}/>
                    <List todoList={todoList} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo}/>
                    <Footer todoList={todoList} checkAllTodo={this.checkAllTodo} clearAllDoneTodo={this.clearAllDoneTodo}/>
                </div>
            </div>
        )
    }
}

Header.jsx

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { nanoid } from 'nanoid'

import './Header.css'

export default class Header extends Component {

    // 对接收的props进行类型、必要性的限制
    static propTypes = {
        addTodo: PropTypes.func.isRequired
    }

    handleKeyUp = (event) => {
        // 解构赋值获取target, key
        const {target, key} = event
        // 判断是否是回车
        if (key !== 'Enter') {
            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>
        )
    }
}

List.jsx

import React, { Component } from 'react'
import PropTypes from 'prop-types'

import Item from '../Item/Item'

import './List.css'

export default class List extends Component {

    // 对接收的props进行类型、必要性的限制
    static propTypes = {
        todoList: PropTypes.array.isRequired,
        updateTodo: PropTypes.func.isRequired,
        deleteTodo: PropTypes.func.isRequired
    }

    render() {
        const {todoList, updateTodo, deleteTodo} = this.props
        return (
            <ul className="todo-main">
                {
                    todoList.map((todo) => { 
                        return <Item key={todo.id} {...todo} updateTodo={updateTodo} deleteTodo={deleteTodo}/>
                    })
                }
            </ul>
        )
    }
}

Item.jsx

import React, { Component } from 'react'

import './Item.css'

export default class Item extends Component {

    state = {mouse: false}

    // 鼠标移入移出的回调
    handleMouse = flag => {
        return () => {
            this.setState({mouse: flag})
        }
    }

    // 勾选、取消某一个todo的回调
    handleChange = id => {
        return (event) => {
            this.props.updateTodo(id, event.target.checked)
        }
    }

    // 删除一个todo的回调
    handleDelete = (id) => {
        if (window.confirm('确定删除吗?')) { // 此处要加window,否则会报错Unexpected use of 'confirm'.
            this.props.deleteTodo(id)
        }
    }

    render() {
        const {id, name, done} = this.props
        const {mouse} = this.state
        return (
            <li style={{backgroundColor: mouse ? '#ddd' : '#fff'}} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}>
                <label>
                    <input type="checkbox" checked={done} onChange={this.handleChange(id)}/>
                    <span>{name}</span>
                </label>
                <button onClick={() => this.handleDelete(id) } className="btn btn-danger" style={{display: mouse ? 'block' : 'none'}}>删除</button>
            </li>
        )
    }
}

Footer.jsx

import React, { Component } from 'react'

import './Footer.css'

export default class Footer extends Component {

    // 全选checkbox的回调
    handleChangeAll = (event) => {
        this.props.checkAllTodo(event.target.checked)
    }

    // 清除已完成任务的回调
    handleClearAll = () => {
        
    }

    render() {

        const {todoList} = this.props

        // 已完成的个数
        const doneCount = todoList.reduce((pre, currentTodo) => pre + (currentTodo.done ? 1 : 0), 0) // pre上一次的返回值
        // console.log('@', doneCount)

        // 总数
        const total = todoList.length

        // 清除已完成任务的回调
        this.handleClearAll = () => {
            this.props.clearAllDoneTodo()
        }

        return (
            <div className="todo-footer">
                <label>
                    <input type="checkbox" onChange={this.handleChangeAll} checked={doneCount === total && total !== 0}/>
                </label>
                <span>
                    <span>已完成{doneCount}</span> / 全部{total}
                </span>
                <button onClick={this.handleClearAll} className="btn btn-danger">清除已完成任务</button>
            </div>
        )
    }
}

GIF 2022-4-5 11-45-48

总结

  1. 拆分组件、实现静态组件,注意:classNamestyle的写法
  2. 动态初始化列表,如何确定将数据放在哪个组件的state中:
    • 某个组件使用:放在自身的state中
    • 某些组件使用:放在他们共同的父组件state中(状态提升)
  3. 关于父子之间通信:
    • 【父组件】给【子组件】传递数据:通过props传递
    • 【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数
  4. 注意defaultCheckedchecked的区别,类似的还有:defaultValue和value
  5. 状态在哪里,操作状态的方法就在哪里


import React, { Component } from 'react'

import './Footer.css'

export default class Footer extends Component {

    // 全选checkbox的回调
    handleChangeAll = (event) => {
        this.props.checkAllTodo(event.target.checked)
    }

    // 清除已完成任务的回调
    handleClearAll = () => {
        
    }

    render() {

        const {todoList} = this.props

        // 已完成的个数
        const doneCount = todoList.reduce((pre, currentTodo) => pre + (currentTodo.done ? 1 : 0), 0) // pre上一次的返回值
        // console.log('@', doneCount)

        // 总数
        const total = todoList.length

        // 清除已完成任务的回调
        this.handleClearAll = () => {
            this.props.clearAllDoneTodo()
        }

        return (
            <div className="todo-footer">
                <label>
                    <input type="checkbox" onChange={this.handleChangeAll} checked={doneCount === total && total !== 0}/>
                </label>
                <span>
                    <span>已完成{doneCount}</span> / 全部{total}
                </span>
                <button onClick={this.handleClearAll} className="btn btn-danger">清除已完成任务</button>
            </div>
        )
    }
}

总结

  1. 拆分组件、实现静态组件,注意:classNamestyle的写法
  2. 动态初始化列表,如何确定将数据放在哪个组件的state中:
    • 某个组件使用:放在自身的state中
    • 某些组件使用:放在他们共同的父组件state中(状态提升)
  3. 关于父子之间通信:
    • 【父组件】给【子组件】传递数据:通过props传递
    • 【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数
  4. 注意defaultCheckedchecked的区别,类似的还有:defaultValue和value
  5. 状态在哪里,操作状态的方法就在哪里

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/126498.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++程序员学习资料汇总

小白入门 计算机网络微课堂&#xff08;有字幕无背景音乐版&#xff09; 非常适合小白学习&#xff0c;没有废话&#xff0c;非常生动 《计算机是怎样跑起来的》 《程序是怎样跑起来的》 《网络是怎样连接的》 基础 资料名备注状态阅读时间《深入理解计算机系统》很多大厂面…

实验室预约系统|基于Springboot+Vue实现学校实验室预约管理系统

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、掘金特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、毕业设计、简历模板、学习资料、面试题库、技术互助 收藏点赞不迷路 关注作者有好处 文末获取源…

JAVA中那些令人眼花缭乱的锁

一、开局一张图带你了解java相关的锁 二、乐观锁和悲观锁 1、悲观锁 悲观锁对应于生活中悲观的人&#xff0c;悲观的人总是想着事情往坏的方向发展。 举个生活中的例子&#xff0c;假设厕所只有一个坑位了&#xff0c;悲观锁上厕所会第一时间把门反锁上&#xff0c;这样其他…

python pyqtgraph绘图库

pyqtgraph官网 PyQtGraph被大量应用于Qt GUI平台&#xff08;通过PyQt或PySide&#xff09;&#xff0c;因为它的高性能图形和numpy可用于大量数据处理。 特别注意的是&#xff0c;pyqtgraph使用了Qt的GraphicsView框架&#xff0c;它本身是一个功能强大的图形系统; 我们将最优…

知识付费海哥:这样做课,不赚钱都难

现在不少人开始了开发网课&#xff0c;卖网课赚钱&#xff0c; 但是在网课开发时&#xff0c;很多人开始的时候&#xff0c;关注的点就错了&#xff01; 自己喜欢钓鱼&#xff0c;就开发钓鱼的课&#xff0c; 自己喜欢演讲&#xff0c;就开发演讲的课&#xff0c; 自己喜欢…

Dubbo入门(二)——IDEA下Dubbo+Zookeeper搭建

目录一、Zookeeper1.1 下载1.2 安装1.3 修改配置文件1.4 启动二、Dubbo插件搭建三、手动创建3.1 创建项目3.1.1 pom依赖3.2 api模块3.2.1 pom依赖3.2.2 实体类3.2.3 service接口3.3 provider3.3.1 pom依赖3.3.2 配置文件3.3.3 mapper3.3.4 service实现类3.3.5 启动类3.4 consum…

冶金工艺流程(钢铁全流程)

工艺图 工艺讲解 生产流程从矿石原料采集开始,对于低品位的矿石&#xff0c;必须在冶炼前经选矿工序先选出铁精矿&#xff0c;然后进一步制成烧结矿或球团矿。 ————————————————————————————— 高炉冶炼是一个连续的、大规模的高温生产过程。铁矿石…

为什么Aruba始终走在网络世界的前列?

编辑 | 阿冒 设计 | 沐由关于网络的价值&#xff0c;梅特卡夫定律&#xff08;Metcalfes law&#xff09;曾经在多年前就明白无误地告诉我们&#xff1a;VKN。其中&#xff0c;V代表一个网络的价值&#xff0c;N代表这个网络的节点数&#xff0c;K代表价值系数。如果梅特卡夫…

47. 批量规范化 代码从零开始实现 + 简洁实现

1. 从零实现 下面&#xff0c;我们从头开始实现一个具有张量的批量规范化层。 import torch from torch import nn from d2l import torch as d2l# X是输入&#xff0c;gamma, beta是两个可以学习的参数&#xff0c;moving_mean, moving_var是整个数据集的均值和方差&#xf…

ssh登录——scp传文件

一、ssh登录 1.基本用法 远程登录服务器&#xff1a; ssh userhostname# user: 用户名 # hostname: IP地址或域名 第一次登录时会提示&#xff1a; The authenticity of host 123.57.47.211 (123.57.47.211) cant be established. ECDSA key fingerprint is SHA256:iy237y…

十分钟掌握 “UML ” 的基本使用

十分钟掌握 “UML ” 的基本使用 每博一文案 很喜欢这样一段话&#xff1a;内可以不跟别人计较&#xff0c;但一定要学会维护自己&#xff0c;你可以不跟别人争抢&#xff0c;但应该懂得远离小人。 人生在世&#xff0c;我们会遇到形形色色的人&#xff0c;人心隔肚皮&#xf…

Pytorch深度学习实战——第3章课后习题

1.从list(range(9))list(range(9))list(range(9))中创建一个张量aaa并预测检查其大小、偏移量、步长。 import torch atorch.tensor(list(range(9))) print(a.storage_offset())#偏移量 print(a.stride())#步长 print(a.size())#大小a)使用ba.view(3,3)ba.view(3,3)ba.view(3,…

Aspose.PDF for Java系列3-创建复杂的PDF

创建复杂PDF 上篇文章展示了使用Java和Aspose.PDF简单步骤。本文中&#xff0c;我们将使用Java和Aspose.PDF创建更复杂的文档结构。此示例中&#xff0c;我们将创建包含图片、两个文本片段&#xff08;标题和段落&#xff09;和表格。 创建文档参考以下步骤&#xff1a; 实例…

被房地产坑惨的老板电器,终于要“回血”了!

近期&#xff0c;随着房地产行业重新被定义为“国民经济的支柱产业”&#xff0c;叠加疫情防控转向&#xff0c;笼罩在家电行业上空的阴霾逐渐消散&#xff0c;家电股预期改善&#xff0c;纷纷迎来估值修复。尤其是曾遭房地产暴击的老板电器&#xff0c;有望抓住房地产回暖的窗…

Synchronized

Synchronized前言1.synchronized修饰符和线程控制1.1synchronized修饰符和Object Monitor模式1.2 synchronized修饰符可标注的位置2. 方法2.1 waitwait多态表达式notify()方法和notifyAII()方法interrupt中断信号判断是否收到interrupt中断信号的方法前言 悲观锁在Java中有两种…

Jenkins环境搭建

Jenkins环境搭建 一、Jenkins下载&#xff1a;Jenkins 点击Download&#xff0c;进入新的页面后点击下载Jenkins的war包&#xff0c;如下图&#xff1a; 注意&#xff1a;Jenkins是java语言开发&#xff0c;需要安装JDK并进行环境变量配置&#xff0c;Jenkins可以在Tomcat容器…

java注解(Annotation)和反射(Reflection)

文章目录重要的点一 注解(Annotation)(Annotation)(Annotation)1.1 注解初识1.2 内置注解1.3 内置注解代码演示1.4 元注解(meta−annotation)(meta-annotation)(meta−annotation)1.5 元注解代码演示1.6 自定义注解1.7 自定义注解代码演示二 反射(Reflection)(Reflection)(Refl…

gitlab-ci.yml关键字(三)before_script 、script 、after_script

before_script before_script 用于定义在每个作业执行之前所运行的一系列脚本命令。这里需要注意的是&#xff0c;before_script 运行的时机&#xff0c;是在制品&#xff08;artifacts&#xff09;恢复后&#xff0c;也就意味着&#xff0c;在这个时机中是可以操作制品的。 …

Unity 解决QFramework WebGL报错

Unity 解决QFramework WebGL报错&#x1f95d;错误描述☕原因&#x1f364; 解决方案&#x1f95d;错误描述 在新建工程中只导入了QFramework&#xff0c;Build WebGL(空场景) 会看到如下错误&#xff1a; Exception: Non-Public Constructor() not found! in QFramework.Saf…

点云 3D 目标检测 - SECOND(Sensors 2018)

点云 3D 目标检测 - SECOND&#xff08;Sensors 2018&#xff09;摘要1. 引言2. 相关工作2.1 基于前视图和图像的方法2.2 基于鸟瞰图的方法2.3 基于3D的方法2.4 基于融合的方法3. SECOND检测器3.1 网络架构3.1.1 点云分组3.1.2 Voxelwise特征提取器3.1.3 稀疏卷积中间提取器3.1…