React:五、React脚手架应用

React:五、React脚手架应用

1 前言

react脚手架搭建react应用:

1.全局安装(任意地方可使用create-react-app) 
npm install -g create-react-app
或 npm i -g create-react-app (i代表install)

2.切换到想创建项目的目录,使用如下命令
create-react-app appName:如 hello-my-react

执行第二步前,可以打开cmd,做如下配置,再次执行第二步会快些:

npm config set registry https://registry.npm.taobao.org
-- 配置后可通过下面方式来验证是否成功
npm config get registry
-- 或npm info express

出现如下即创建成功
在这里插入图片描述
然后通过vscode打开项目文件夹,ctrl+`,执行npm start:
在这里插入图片描述

2 使用

2.1 项目结构介绍

public一般存放静态资源:

.ico:网站的图标

index.html:

%PUBLIC_URL%可改为.(当前路径):
在这里插入图片描述
public的index.html(react应用仅1个index.html,即SPA,single page web application 单页应用程序):

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <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>
  </body>
</html>

在这里插入图片描述
src文件夹:
在这里插入图片描述
在这里插入图片描述
降低项目的react版本:

在这里插入图片描述
打开vscode的terminal终端,输入如下命令:

npm i react@17 react-dom@17

如下即成功:
在这里插入图片描述
package.json更新了react和react-dom的版本:
在这里插入图片描述
降低react版本后,需要修改src下的index.js(注意修改:import ReactDOM from ‘react-dom’;):

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"))

reportWebVitals();

修改App.js:

import React, { Component } from 'react'

// Component 在react.js中是分别暴露的,即  export Component,即可使用import React, { Component } from 'react'
// 如果是默认暴露:export default App,在导入时,可以使用import 别名
class App extends Component{
  render(){
    return (
      <div>
        hello,react
      </div>
    )
  }
}

export default App;

2.2 组件化编程

在这里插入图片描述
hello.css:

.hel{
    width: 200px;
    height: 40px;
    background-color: blueviolet;
}

hello.js:

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

class Hello extends Component{
    render(){
        return (
            <div className='hel'>
                我是hello组件
            </div>
        )
    }
}

export default Hello

welcome.css:

.wel{
    width: 400px;
    height: 40px;
    background-color: yellowgreen;
}

welcome.js:

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

class Welcome extends Component{
    render(){
        return (
            <div className = 'wel'>
                我是welcome组件
            </div>
        )
    }
}

export default Welcome

App.js:

import React,{Component} from "react";
import Hello  from "./Components/hello/hello";
import Welcome from "./Components/welcome/welcome";


class App extends Component{
  render(){
    return (
      <div>
        <Hello/>
        <Welcome/>
      </div>
    )
  }
}
export default App;

2.3 ToDo组件

2.3.1 父子组件通信

安装快捷语法插件,rcc可快速输入react的jsx语法:

在这里插入图片描述

//在某个组件下的index.jsx中输入rcc,快捷提示输出如下:
import React, { Component } from 'react'

export default class index extends Component {
  render() {
    return (
      <div>index</div>
    )
  }
}

在这里插入图片描述
Footer:
index.css:

/* footer */
.todo-footer label{
    display: inline-block;
    margin-right: 16px;
}

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

.todo-footer button{
    float: right;
    margin-bottom: 1px;
}

.todo-footer span{
    font-size: 16px;
}

index.jsx:

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

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

Header:
index.css:

.todo-header span{
    display: inline-block;
    width: 70px;
    font-size: 16px;
    text-shadow: 1px 1px rgb(214, 211, 211);
}
/* heaer */
.todo-header input{
    width:620px;
    padding: 4px;
    height:28px;
    border:1px solid #ccc;
    border-radius: 5px;
}

.todo-header input::placeholder{
    font-size: 14px;
    padding-left: 10px;
    text-align: center;
    color: rgb(139, 134, 134);
}

.todo-header input:focus{
    outline: none;
    border-color: red;
    box-shadow: 1px 1px rgb(238, 103, 103);
}

index.jsx:

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


export default class Header extends Component {

  // 一般最好判断输入成功是键盘按下键并弹起的时候,即keyUp事件,
  // 不易使用keyDown事件
  handlekeyUp = (event)=>{
      let val = event.target.value;
      let keyCode = event.keyCode;
      if(keyCode=='13'){
        // console.log("新的",val)
        // console.log(event.keyCode)
        // console.log(parseInt("00012"))
        let max_str_id = this.getMaxId(this.props.originData)
        let obj = {id:max_str_id,name:val,done:false}
        this.props.onShow(obj)
      }
  }

  getMaxId(...objs){
    // console.log(objs)
    let arr = objs[0]
    if(arr.length<=0){
      throw new Error("app父组件的数组长度需大于0")
    }
    let max = arr[0].id;
    arr.forEach((o,inedx)=>{
      // console.log(o,inedx)
      if(o.id>max){
        max = o.id
      }
    })
    // 子组件Header构造对象obj的id是当前最大id+1,补00凑成3位返回给父组件
    // slice(-3):取数组倒数第三个到最后一个元素(包含最后一个)
    // Array(3).join(0):构造2个0的字符串(因为3个元素间隔只有2个),拼接最大id
    let maxAdd = parseInt(max)+1
    let newId = (Array(3).join(0)+parseInt(maxAdd)).slice(-3)
    return newId
  }

  removeSpace = (event)=>{
    event.target.value = event.target.value.replace(/\s+/g,"")
  }
  // 禁止输入空格,keyUp和keyDown效果均不佳,监听input较优
  inputRemoveSpace = (event)=>{
    event.target.value = event.target.value.replace(/\s+/g,"")
  }
  render() {
    return (
      <div className='todo-header'>
        <span>请输入:</span>
        <input 
        onKeyUp = {this.handlekeyUp} 
        onKeyDown = {this.removeSpace}
        onInput = {this.inputRemoveSpace}
        type="text" 
        placeholder='请输入任务名称,按回车确定'/>
      </div>
    )
  }
}

Item:
index.css:

li{
    list-style: none;
    box-sizing: border-box;
    height:36px;
    width:630px;
    line-height: 36px;
    padding: 0 5px;
    border: 1px solid #ddd;
    border-radius: 5px;
    margin:5px 0;
}

li span{
    display: inline-block;
    vertical-align: middle;
    height:40px;
    font-size: 14px;
    font-family: Arial, Helvetica, sans-serif;
    letter-spacing: 1px;
    padding-left: 5px;
}

li input,li,li span{
    cursor: pointer;
}

li input{
    width: 16px;
    height: 16px;
}

li:hover{
    background-color: rgb(203, 241, 229);
}

index.jsx:

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


export default class Item extends Component {
  render() {
    // const {content} = this.props
    const {id,name,done} = this.props
    return (
        <li>
            <label htmlFor="">
                <input type="checkbox" defaultChecked={done}/> 
                <span>{name}</span>
                {/* <span>{content.name}</span> */}
            </label>
            <button className='btn btn-danger' style={{display:"none"}}>删除</button>
        </li>
    )
  }
}

Main:
index.css:

.todo-main{
    padding-left: 70px;
}

index.jsx:

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

export default class Main extends Component {
  render() {
    const {myTodos} = this.props
    return (
      <ul className='todo-main'>
        {
          myTodos.map((per)=>{
            // return <Item key={per.id} content = {per}/>
            return <Item key={per.id} {...per}/>
          })
        }
      </ul>
    )
  }
}

App.css:

body{
  background: #fff;
}

.todo-container{
  width: 760px;
  border: 1px solid rgb(123, 121, 121);
  border-radius: 5px;
  margin: 10px auto;
}

.todo-container .wrap{
  margin: 10px 5px;
}

.btn{
  display: inline-block;
  background-color: aquamarine;
  border: 1px solid #ddd;
  padding: 5px 10px;
  border-radius: 5px;
  color: rgb(223, 19, 19);
  font-family:Verdana, Geneva, Tahoma, sans-serif;
  font-weight: 600;
}

.btn:hover{
  cursor: pointer;
  opacity: 0.8;
}

.btn:focus{
  background-color: yellowgreen;
  opacity: 0.8;
}

App.js:

import React, { Component } from 'react'
// 如果是导入js文件,那么文件名可以省略.js,如果是
// 导入css文件,文件名不可省略.css
/* 
如果是组件文件夹(文件夹命名是类名命名形式,即大写字母开头),
那么下面可以直接写index.css、index.jsx,直接引入大写类名即可
*/
import Header from './Components/Header'
import Main from './Components/Main'
import Footer from './Components/Footer'
import './App.css'

window.a = "da"
// Component 在react.js中是分别暴露的,即  export Component,即可使用import React, { Component } from 'react'
// 如果是默认暴露:export default App,在导入时,可以使用import 别名
class App extends Component{

  state = {todo:[
    {id:"001",name:"学习python",done:true},
    {id:"002",name:"学java",done:true},
    {id:"003",name:"学react",done:true},
  ]}

  // 组件中,子组件要传数据给父组件,即组件通信中的的子传父,需要
  // 父组件将函数用过props传给子组件,子组件需要传数据给父组件时,调用
  // 父组件的这个方法即可
  sonToFather = (obj)=>{
    const {todo} = this.state
    this.setState({todo:[...todo,obj]})
    console.log(this.state)
  }
  render(){
    console.log(window.a)
    const {todo} = this.state
    return (
      <div className='todo-container'>
        <div className='wrap'>
          <Header onShow = {this.sonToFather} originData = {todo}/>
          <Main myTodos = {this.state.todo}/>
          <Footer/>
        </div>
      </div>
    )
  }
}

export default App;

效果如下:
在这里插入图片描述
通信方式:父组件传数据给子组件,依赖props传值;子组件传数据给父组件,父组件将父组件定义的更新state的方法,通过props传递给子组件,子组件传值时,调用父组件传来的props方法即可。

2.3.2 爷孙组件通信

state状态在哪里,操作的方法就在哪里

Header:

index.css:

.todo-header span{
    display: inline-block;
    width: 70px;
    font-size: 16px;
    text-shadow: 1px 1px rgb(214, 211, 211);
}
/* heaer */
.todo-header input{
    width:620px;
    padding: 4px;
    height:28px;
    border:1px solid #ccc;
    border-radius: 5px;
}

.todo-header input::placeholder{
    font-size: 14px;
    padding-left: 10px;
    text-align: center;
    color: rgb(139, 134, 134);
}

.todo-header input:focus{
    outline: none;
    border-color: red;
    box-shadow: 1px 1px rgb(238, 103, 103);
}

index.jsx:

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


export default class Header extends Component {

  // 一般最好判断输入成功是键盘按下键并弹起的时候,即keyUp事件,
  // 不易使用keyDown事件
  handlekeyUp = (event)=>{
      let val = event.target.value;
      let keyCode = event.keyCode;
      if(keyCode=='13'){
        // console.log("新的",val)
        // console.log(event.keyCode)
        // console.log(parseInt("00012"))
        if(val.trim()<=0) return 
        let max_str_id = this.getMaxId(this.props.originData)
        let obj = {id:max_str_id,name:val,done:false}
        this.props.onShow(obj)
      }
  }

  getMaxId(...objs){
    // console.log(objs)
    let arr = objs[0]
    if(arr.length<=0){
      throw new Error("app父组件的数组长度需大于0")
    }
    let max = arr[0].id;
    arr.forEach((o,inedx)=>{
      // console.log(o,inedx)
      if(o.id>max){
        max = o.id
      }
    })
    // 子组件Header构造对象obj的id是当前最大id+1,补00凑成3位返回给父组件
    // slice(-3):取数组倒数第三个到最后一个元素(包含最后一个)
    // Array(3).join(0):构造2个0的字符串(因为3个元素间隔只有2个),拼接最大id
    let maxAdd = parseInt(max)+1
    let newId = (Array(3).join(0)+parseInt(maxAdd)).slice(-3)
    return newId
  }

  removeSpace = (event)=>{
    event.target.value = event.target.value.replace(/\s+/g,"")
  }
  // 禁止输入空格,keyUp和keyDown效果均不佳,监听input较优
  inputRemoveSpace = (event)=>{
    event.target.value = event.target.value.replace(/\s+/g,"")
  }
  render() {
    return (
      <div className='todo-header'>
        <span>请输入:</span>
        <input 
        onKeyUp = {this.handlekeyUp} 
        onKeyDown = {this.removeSpace}
        onInput = {this.inputRemoveSpace}
        type="text" 
        placeholder='请输入任务名称,按回车确定'/>
      </div>
    )
  }
}

Main:
index.css:

.todo-main{
    padding-left: 70px;
}

index.jsx:

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

export default class Main extends Component {
  render() {
    const {myTodos,checkChange} = this.props
    return (
      <ul className='todo-main'>
        {
          myTodos.map((per)=>{
            return <Item key={per.id} {...per} checkChange = {checkChange}/>
          })
        }
      </ul>
    )
  }
}

Footer:
index.css:

/* footer */
.todo-footer label{
    display: inline-block;
    margin-right: 16px;
}

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

.todo-footer button{
    float: right;
    margin-bottom: 1px;
}

.todo-footer span{
    font-size: 16px;
}

index.jsx:

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

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

Item:
index.css:

li{
    list-style: none;
    box-sizing: border-box;
    height:36px;
    width:630px;
    line-height: 36px;
    padding: 0 5px;
    border: 1px solid #ddd;
    border-radius: 5px;
    margin:5px 0;
    position: relative;
}

li span{
    display: inline-block;
    vertical-align: middle;
    height:40px;
    font-size: 14px;
    font-family: Arial, Helvetica, sans-serif;
    letter-spacing: 1px;
    padding-left: 5px;
}

li input,li,li span{
    cursor: pointer;
}

li input{
    width: 16px;
    height: 16px;
}

li:hover{
    background-color: rgb(203, 241, 229);
}

li .btn{
    position: absolute;
    top:2px;
    right:15px;
    height:30px;
    float:left;
    border: 1px solid rgb(157, 230, 11);
    vertical-align: middle;
}

index.jsx:

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


export default class Item extends Component {
  state = {isHover:false}

  move = (flag)=>{
    return ()=>{
      this.setState({isHover:flag})
    }
  }

  changeCheck = (id)=>{
    const {checkChange} = this.props
    return (event)=>{
      checkChange(id,event.target.checked)
    }
  }

  render() {
    // const {content} = this.props
    const {id,name,done} = this.props
    return (
        <li 
        style = {{border:this.state.isHover?"1px solid red":"1px solid #ddd"}}
        onMouseLeave={this.move(false)} 
        onMouseEnter={this.move(true)}>
            <label htmlFor="">
                <input type="checkbox" 
                defaultChecked={done}
                onChange= {this.changeCheck(id)}/> 
                <span>{name}</span>
                {/* <span>{content.name}</span> */}
            </label>
            <button 
            className='btn btn-danger' 
            style={{display:this.state.isHover?"block":"none"}}>删除</button>
        </li>
    )
  }
}

App.css:

body{
  background: #fff;
}

.todo-container{
  width: 760px;
  border: 1px solid rgb(123, 121, 121);
  border-radius: 5px;
  margin: 10px auto;
}

.todo-container .wrap{
  margin: 10px 5px;
}

.btn{
  display: inline-block;
  background-color: aquamarine;
  border: 1px solid #ddd;
  padding: 5px 10px;
  border-radius: 5px;
  color: rgb(223, 19, 19);
  font-family:Verdana, Geneva, Tahoma, sans-serif;
  font-weight: 600;
  font-size: 14px;
}

.btn:hover{
  cursor: pointer;
  opacity: 0.8;
}

.btn:focus{
  background-color: yellowgreen;
  opacity: 0.8;
}

App.js:

import React, { Component } from 'react'
// 如果是导入js文件,那么文件名可以省略.js,如果是
// 导入css文件,文件名不可省略.css
/* 
如果是组件文件夹(文件夹命名是类名命名形式,即大写字母开头),
那么下面可以直接写index.css、index.jsx,直接引入大写类名即可
*/
import Header from './Components/Header'
import Main from './Components/Main'
import Footer from './Components/Footer'
import './App.css'

window.a = "da"
// Component 在react.js中是分别暴露的,即  export Component,即可使用import React, { Component } from 'react'
// 如果是默认暴露:export default App,在导入时,可以使用import 别名
class App extends Component{

  // 状态在哪里,操作的方法就在哪里
  state = {todo:[
    {id:"001",name:"学习python",done:true},
    {id:"002",name:"学java",done:true},
    {id:"003",name:"学react",done:true},
  ]}

  // 组件中,子组件要传数据给父组件,即组件通信中的的子传父,需要
  // 父组件将函数用过props传给子组件,子组件需要传数据给父组件时,调用
  // 父组件的这个方法即可
  sonToFather = (obj)=>{
    const {todo} = this.state
    this.setState({todo:[...todo,obj]})
    console.log(this.state)
  }

  // 子组件更改是否勾选
  todoCheckChange = (id,done)=>{
    const {todo} = this.state
    console.log(id,done)
    const newObj = todo.map((obj)=>{
      if(obj.id===id)return {...obj,done:done}
      else return obj
    })
    this.setState({todo:newObj})
}

  render(){
    console.log(window.a)
    const {todo} = this.state
    return (
      <div className='todo-container'>
        <div className='wrap'>
          <Header onShow = {this.sonToFather} originData = {todo}/>
          <Main 
          myTodos = {this.state.todo}
          checkChange = {this.todoCheckChange}/>
          <Footer/>
        </div>
      </div>
    )
  }
}

export default App;

效果如下:
input的checkbox传值给App:
在这里插入图片描述
2.3.3 必要性限制

react脚手架没有安装prop-types,需要自己安装:

npm i prop-types

App.js中对子组件有props传值,需要限制Header和Main组件:
Header:

import React, { Component } from 'react'
import './index.css'
import PropTypes from 'prop-types'


export default class Header extends Component {

  static propTypes={
    onShow:PropTypes.func.isRequired
  }

  // 一般最好判断输入成功是键盘按下键并弹起的时候,即keyUp事件,
  // 不易使用keyDown事件
  handlekeyUp = (event)=>{
      let val = event.target.value;
      let keyCode = event.keyCode;
      if(keyCode=='13'){
        // console.log("新的",val)
        // console.log(event.keyCode)
        // console.log(parseInt("00012"))
        if(val.trim()<=0) return 
        let max_str_id = this.getMaxId(this.props.originData)
        let obj = {id:max_str_id,name:val,done:false}
        this.props.onShow(obj)
      }
  }

  getMaxId(...objs){
    // console.log(objs)
    let arr = objs[0]
    if(arr.length<=0){
      throw new Error("app父组件的数组长度需大于0")
    }
    let max = arr[0].id;
    arr.forEach((o,inedx)=>{
      // console.log(o,inedx)
      if(o.id>max){
        max = o.id
      }
    })
    // 子组件Header构造对象obj的id是当前最大id+1,补00凑成3位返回给父组件
    // slice(-3):取数组倒数第三个到最后一个元素(包含最后一个)
    // Array(3).join(0):构造2个0的字符串(因为3个元素间隔只有2个),拼接最大id
    let maxAdd = parseInt(max)+1
    let newId = (Array(3).join(0)+parseInt(maxAdd)).slice(-3)
    return newId
  }

  removeSpace = (event)=>{
    event.target.value = event.target.value.replace(/\s+/g,"")
  }
  // 禁止输入空格,keyUp和keyDown效果均不佳,监听input较优
  inputRemoveSpace = (event)=>{
    event.target.value = event.target.value.replace(/\s+/g,"")
  }
  render() {
    return (
      <div className='todo-header'>
        <span>请输入:</span>
        <input 
        onKeyUp = {this.handlekeyUp} 
        onKeyDown = {this.removeSpace}
        onInput = {this.inputRemoveSpace}
        type="text" 
        placeholder='请输入任务名称,按回车确定'/>
      </div>
    )
  }
}

Main:

import React, { Component } from 'react'
import Item from '../Item'
import "./index.css"
import PropTypes from 'prop-types'


export default class Main extends Component {
  static propTypes={
    myTodos:PropTypes.array.isRequired,
    checkChange:PropTypes.func.isRequired
  }

  render() {
    const {myTodos,checkChange} = this.props
    return (
      <ul className='todo-main'>
        {
          myTodos.map((per)=>{
            return <Item key={per.id} {...per} checkChange = {checkChange}/>
          })
        }
      </ul>
    )
  }
}

2.3.4 删除item,清除已完成,全选按钮功能补充

Header:

index.css:

.todo-header span{
    display: inline-block;
    width: 70px;
    font-size: 16px;
    text-shadow: 1px 1px rgb(214, 211, 211);
}
/* heaer */
.todo-header input{
    width:620px;
    padding: 4px;
    height:28px;
    border:1px solid #ccc;
    border-radius: 5px;
}

.todo-header input::placeholder{
    font-size: 14px;
    padding-left: 10px;
    text-align: center;
    color: rgb(139, 134, 134);
}

.todo-header input:focus{
    outline: none;
    border-color: red;
    box-shadow: 1px 1px rgb(238, 103, 103);
}

index.jsx:

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


export default class Header extends Component {

  // 一般最好判断输入成功是键盘按下键并弹起的时候,即keyUp事件,
  // 不易使用keyDown事件
  handlekeyUp = (event)=>{
      let val = event.target.value;
      let keyCode = event.keyCode;
      if(keyCode=='13'){
        // console.log("新的",val)
        // console.log(event.keyCode)
        // console.log(parseInt("00012"))
        if(val.trim()<=0) return 
        let max_str_id = this.getMaxId(this.props.originData)
        let obj = {id:max_str_id,name:val,done:false}
        this.props.onShow(obj)
      }
  }

  getMaxId(...objs){
    // console.log(objs)
    let arr = objs[0]
    if(arr.length<0){
      throw new Error("app父组件的数组长度需大于等于0")
    }
    if(arr.length==0){
      return "001";
    }
    let max = arr[0].id;
    arr.forEach((o,inedx)=>{
      // console.log(o,inedx)
      if(o.id>max){
        max = o.id
      }
    })
    // 子组件Header构造对象obj的id是当前最大id+1,补00凑成3位返回给父组件
    // slice(-3):取数组倒数第三个到最后一个元素(包含最后一个)
    // Array(3).join(0):构造2个0的字符串(因为3个元素间隔只有2个),拼接最大id
    let maxAdd = parseInt(max)+1
    let newId = (Array(3).join(0)+parseInt(maxAdd)).slice(-3)
    return newId
  }

  removeSpace = (event)=>{
    event.target.value = event.target.value.replace(/\s+/g,"")
  }
  // 禁止输入空格,keyUp和keyDown效果均不佳,监听input较优
  inputRemoveSpace = (event)=>{
    event.target.value = event.target.value.replace(/\s+/g,"")
  }
  render() {
    return (
      <div className='todo-header'>
        <span>请输入:</span>
        <input 
        onKeyUp = {this.handlekeyUp} 
        onKeyDown = {this.removeSpace}
        onInput = {this.inputRemoveSpace}
        type="text" 
        placeholder='请输入任务名称,按回车确定'/>
      </div>
    )
  }
}

Main:
index.css:

.todo-main{
    padding-left: 70px;
}

index.jsx:

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

export default class Main extends Component {
  render() {
    const {myTodos,checkChange,deleteItem} = this.props
    return (
      <ul className='todo-main'>
        {
          myTodos.map((per)=>{
            return <Item key={per.id} {...per} 
            checkChange = {checkChange}
            deleteItem = {deleteItem} />
          })
        }
      </ul>
    )
  }
}

Item:
index.css:

li{
    list-style: none;
    box-sizing: border-box;
    height:36px;
    width:630px;
    line-height: 36px;
    padding: 0 5px;
    border: 1px solid #ddd;
    border-radius: 5px;
    margin:5px 0;
    position: relative;
}

li span{
    display: inline-block;
    vertical-align: middle;
    height:40px;
    font-size: 14px;
    font-family: Arial, Helvetica, sans-serif;
    letter-spacing: 1px;
    padding-left: 5px;
}

li input,li,li span{
    cursor: pointer;
}

li input{
    width: 16px;
    height: 16px;
}

li:hover{
    background-color: rgb(203, 241, 229);
}

li .btn{
    position: absolute;
    top:3px;
    right:15px;
    height:28px;
    float:left;
    border: 1px solid rgb(157, 230, 11);
}

index.jsx:

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


export default class Item extends Component {

  state = {isHover:false}

  move = (flag)=>{
    return ()=>{
      this.setState({isHover:flag})
    }
  }

  changeCheck = (id)=>{
    const {checkChange} = this.props
    return (event)=>{
      checkChange(id,event.target.checked)
    }
  }

  // 删除todo回调
  handleDelete = (id)=>{
    const {deleteItem} = this.props
    return ()=>{
      if(window.confirm('你确定删除这个东西?')){
        deleteItem(id)
      }
    }
  }

  render() {
    // const {content} = this.props
    const {id,name,done} = this.props
    return (
        <li 
        style = {{border:this.state.isHover?"1px solid red":"1px solid #ddd"}}
        onMouseLeave={this.move(false)} 
        onMouseEnter={this.move(true)}>
            <label htmlFor="">
                <input type="checkbox" 
                checked={done}
                onChange= {this.changeCheck(id)}/> 
                <span>{name}</span>
                {/* <span>{content.name}</span> */}
            </label>
            <button 
            onClick = {this.handleDelete(id)}
            className='btn btn-danger' 
            style={{display:this.state.isHover?"block":"none"}}>删除</button>
        </li>
    )
  }
}

Footer:
index.css:

/* footer */
.todo-footer label{
    display: inline-block;
    margin-right: 16px;
}

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

.todo-footer button{
    float: right;
    margin-bottom: 1px;
}

.todo-footer span{
    font-size: 16px;
}

index.jsx:

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

export default class Footer extends Component {

  finished = ()=>{
      const {myTodos} = this.props
      return myTodos.filter((item)=>{
          return item.done==true;
      }).length
  }

  confirmCheckAll = (event)=>{
    console.log(event.target.checked)
    this.props.checkAll(event.target.checked)
  }

  clearFinished = ()=>{
    const {clearAllFinished} = this.props
    if(window.confirm('是否清除全部已完成的事情呢?')){
      clearAllFinished()
    }
  }

  render() {
    const {myTodos} = this.props
    return (
      <div className='todo-footer'>
        <label htmlFor="">
            <input 
            type="checkbox"
            // onClick = {this.confirmCheckAll}
            onChange = {this.confirmCheckAll}
            checked = {this.finished()==myTodos.length&&myTodos.length!=0?true:false}/>
        </label>
        <span>
            <span>已完成{this.finished()}</span> <span>/ 全部{myTodos.length}</span>
        </span>
        <button 
        className='btn btn-danger'
        onClick = {this.clearFinished}>清除已完成任务</button>
      </div>
    )
  }
}

App.css:

body{
  background: #fff;
}

.todo-container{
  width: 760px;
  border: 1px solid rgb(123, 121, 121);
  border-radius: 5px;
  margin: 10px auto;
}

.todo-container .wrap{
  margin: 10px 5px;
}

.btn{
  display: inline-block;
  background-color: aquamarine;
  border: 1px solid #ddd;
  padding: 5px 10px;
  border-radius: 5px;
  color: rgb(223, 19, 19);
  font-family:Verdana, Geneva, Tahoma, sans-serif;
  font-weight: 600;
}

.btn:hover{
  cursor: pointer;
  opacity: 0.8;
}

.btn:focus{
  background-color: yellowgreen;
  opacity: 0.8;
}

App.js:

import React, { Component } from 'react'
// 如果是导入js文件,那么文件名可以省略.js,如果是
// 导入css文件,文件名不可省略.css
/* 
如果是组件文件夹(文件夹命名是类名命名形式,即大写字母开头),
那么下面可以直接写index.css、index.jsx,直接引入大写类名即可
*/
import Header from './Components/Header'
import Main from './Components/Main'
import Footer from './Components/Footer'
import './App.css'

window.a = "da"
// Component 在react.js中是分别暴露的,即  export Component,即可使用import React, { Component } from 'react'
// 如果是默认暴露:export default App,在导入时,可以使用import 别名
class App extends Component{

  // 状态在哪里,操作的方法就在哪里
  state = {todo:[
    {id:"001",name:"学习python",done:true},
    {id:"002",name:"学java",done:true},
    {id:"003",name:"学react",done:true},
  ]}

  // 组件中,子组件要传数据给父组件,即组件通信中的的子传父,需要
  // 父组件将函数用过props传给子组件,子组件需要传数据给父组件时,调用
  // 父组件的这个方法即可
  sonToFather = (obj)=>{
    const {todo} = this.state
    this.setState({todo:[...todo,obj]})
    console.log(this.state)
  }

  // 子组件更改是否勾选
  todoCheckChange = (id,done)=>{
    const {todo} = this.state
    console.log(id,done)
    const newObj = todo.map((obj)=>{
      if(obj.id===id)return {...obj,done:done}
      else return obj
    })
    this.setState({todo:newObj})
  }

  // 删除item
  deleteItem = (id)=>{
    const {todo} = this.state
    // const newTodo = todo.filter((item,index)=>{
    //   if(item.id!==id)return item;
    //   else return;
    // })
    const newTodo = todo.filter((item,index)=>{
      return item.id!==id;
    })
    // console.log("现在的id是",id)
    // console.log("新的对象",newTodo)
    this.setState({todo:newTodo})
  }

  /* Footer勾选全部数据 */
  checkAll = (isCheck)=>{
    const newTodo = this.state.todo.map(item=>{
      return {...item,done:isCheck};
    })
    this.setState({todo:newTodo})
  }

  /* Footer 清除所有已完成的todo */
  clearAllFinished = ()=>{
    const newTodo = this.state.todo.filter(item=>{
        return item.done===false;
    })
    this.setState({todo:newTodo})
  }


  render(){
    console.log(window.a)
    const {todo} = this.state
    return (
      <div className='todo-container'>
        <div className='wrap'>
          <Header onShow = {this.sonToFather} originData = {todo}/>
          <Main 
          myTodos = {this.state.todo}
          checkChange = {this.todoCheckChange}
          deleteItem = {this.deleteItem} />
          <Footer 
          myTodos = {this.state.todo}
          checkAll = {this.checkAll}
          clearAllFinished = {this.clearAllFinished}/>
        </div>
      </div>
    )
  }
}

export default App;

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2.3.5 小结

1 拆分组件、实现静态组件,注意:className、style的写法;

2 数据位于哪个组件的state?

2.1 某个组件使用,放在自身的state中;
2.2 某些组件使用,放在它们共同的父组件的state中;

3 父子组件通信:

3.1 父组件给子组件传递数据:通过props传递
3.2 子组件给父组件传递数据:通过props传递,要求父提前给子传递一个函数

4 注意defaultChecked和checked的区别,类似还有defaultValue和value

4.1 defaultChecked只会更新一次结果,后续即便传值更新也不会更改;checked会更新传值更新,故而上述勾选框使用属性为checked,非defaultChecked,且checked属性应当为变量,不是常量true或false,否则点击勾选一直不会更改

5 状态(state)在哪里,操作状态的方法就在哪里,如上述App.js父组件

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值