React 2.0


# 收集表单数据

# 非受控组件

页面内所有的输入类 dom,如果是 现用现取 ,那就是非受控组件

<script type="text/babel">
  class Login extends React.Component {
    handleSubmit = (event) => {
      event.preventDefault(); //阻止表单提交
      const { username, password } = this;
      alert(
        `你输入的用户名是:${username.value},你输入的密码是:${password.value}`
      );
    };
    render() {
      return (
        <form onSubmit={this.handleSubmit}>
          用户名:
          <input ref={(c) => (this.username = c)} type="text" name="username" />
          密码:
          <input
            ref={(c) => (this.password = c)}
            type="password"
            name="password"
          />
          <button>登录</button>
        </form>
      );
    }
  }
  ReactDOM.render(<Login />, document.getElementById("test"));
</script>

# 受控组件

页面中所有输入类的 dom, 随着你的输入维护你的状态就是受控组件(相当于 Vue 中的 v-model)

  • 案例一

    <script type="text/babel">
      //创建组件
      class Login extends React.Component {
        //初始化状态
        state = {
          username: "", //用户名
          password: "", //密码
        };
    
        //保存用户名到状态中
        saveUsername = (event) => {
          this.setState({ username: event.target.value });
        };
    
        //保存密码到状态中
        savePassword = (event) => {
          this.setState({ password: event.target.value });
        };
    
        //表单提交的回调
        handleSubmit = (event) => {
          event.preventDefault(); //阻止表单提交
          const { username, password } = this.state;
          alert(`你输入的用户名是:${username},你输入的密码是:${password}`);
        };
    
        render() {
          return (
            <form onSubmit={this.handleSubmit}>
              用户名:
              <input onChange={this.saveUsername} type="text" name="username" />
              密码:
              <input
                onChange={this.savePassword}
                type="password"
                name="password"
              />
              <button>登录</button>
            </form>
          );
        }
      }
      //渲染组件
      ReactDOM.render(<Login />, document.getElementById("test"));
    </script>
  • 案例二 (脚手架)

    import { Component } from "react";
    class FormDemo extends Component {
      state = {
        msg: "hello world",
        isAgree: false,
        comment: "",
        likes: ["vue"],
      };
      render() {
        const { msg, isAgree, comment, likes } = this.state;
        // react 希望用户制作成受控表单, value 与 state 关联,并且使用对应的 onchange 事件修改关联的 state
        // 如果不想修改,或者不想制作成受控表单,可以将 value 改为 defaultValue
        // input:text password number  checkbox  textarea 都要弄成受控表单
        return (
          <div>
            <input
              id="msg"
              type="text"
              value={msg}
              onChange={this.handleChange}
            />
            <input
              id="isAgree"
              type="checkbox"
              checked={isAgree}
              onChange={this.handleChange}
            />
            <textarea
              id="comment"
              value={comment}
              onChange={this.handleChange}
            ></textarea>
            <hr />
            <h3>你喜欢的技术</h3>
            Vue
            <input
              type="checkbox"
              value="vue"
              checked={likes.includes("vue")}
              onChange={this.chooseLikes}
            />
            react
            <input
              type="checkbox"
              value="react"
              checked={likes.includes("react")}
              onChange={this.chooseLikes}
            />
            angular
            <input
              type="checkbox"
              value="angular"
              checked={likes.includes("angular")}
              onChange={this.chooseLikes}
            />
            微信小程序
            <input
              type="checkbox"
              value="小程序"
              checked={likes.includes("小程序")}
              onChange={this.chooseLikes}
            />
          </div>
        );
      }
    
      handleChange = (e) => {
        const target = e.target;
        // 如果 target.type 是 checkbox 我们获取的值就是 target.checked 否则就是 target.value
        const res = target.type === "checkbox" ? target.checked : target.value;
        this.setState({
          [target.id]: res,
        });
      };
      chooseLikes = (e) => {
        const target = e.target;
        const { likes } = this.state;
        // 尽量不要对state进行直接修改
        if (target.checked) {
          this.setState({
            likes: [...likes, target.value],
          });
        } else {
          this.setState({
            likes: likes.filter((item) => item !== target.value),
          });
        }
      };
    }
    export default FormDemo;

# 高阶函数和函数柯里化

  • 高阶函数:如果一个函数符合下面 2 个规范中的任何一个,那该函数就是高阶函数。

    • 若 A 函数,接收的参数是一个函数,那么 A 就可以称之为高阶函数。
    • 若 A 函数,调用的返回值依然是一个函数,那么 A 就可以称之为高阶函数。
      常见的高阶函数有:Promise、setTimeout、arr.map () 数组身上常见的方法等等
  • 函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。

    function sum(a) {
      return (b) => {
        return (c) => {
          return a + b + c;
        };
      };
    }

# 使用柯里化的实现

<script type="text/babel">
  class Login extends React.Component {
    state = {
      username: "", //用户名
      password: "", //密码
    };

    //保存表单数据到状态中
    saveFormData = (dataType) => {
      return (event) => {
        this.setState({ [dataType]: event.target.value });
      };
    };

    //表单提交的回调
    handleSubmit = (event) => {
      event.preventDefault(); //阻止表单提交
      const { username, password } = this.state;
      alert(`你输入的用户名是:${username},你输入的密码是:${password}`);
    };
    render() {
      return (
        <form onSubmit={this.handleSubmit}>
          用户名:
          {/* 如果函数不加小括号就是将这个函数作为onChange的回调*/}
          {/* 如果函数加了小括号就是将这个函数的返回值作为onChange的回调*/}
          <input
            onChange={this.saveFormData("username")}
            type="text"
            name="username"
          />
          密码:
          <input
            onChange={this.saveFormData("password")}
            type="password"
            name="password"
          />
          <button>登录</button>
        </form>
      );
    }
  }
  ReactDOM.render(<Login />, document.getElementById("test"));
</script>

# 不使用柯里化的实现

<script type="text/babel">
  class Login extends React.Component {
    state = {
      username: "", //用户名
      password: "", //密码
    };

    //保存表单数据到状态中
    saveFormData = (dataType, event) => {
      this.setState({ [dataType]: event.target.value });
    };

    //表单提交的回调
    handleSubmit = (event) => {
      event.preventDefault(); //阻止表单提交
      const { username, password } = this.state;
      alert(`你输入的用户名是:${username},你输入的密码是:${password}`);
    };
    render() {
      return (
        <form onSubmit={this.handleSubmit}>
          用户名:
          <input
            onChange={(event) => this.saveFormData("username", event)}
            type="text"
            name="username"
          />
          密码:
          <input
            onChange={(event) => this.saveFormData("password", event)}
            type="password"
            name="password"
          />
          <button>登录</button>
        </form>
      );
    }
  }
  ReactDOM.render(<Login />, document.getElementById("test"));
</script>

# 生命周期 (旧)

只有类组件有生命周期,函数式组件没有生命周期!!!
生命周期函数不写成箭头函数也可以使用 this
组件的生命周期分为三个阶段: 挂载 (组件首次加载), 更新 (组件的 state 和 props 发生改变), 卸载 (组件的 dom 消失)

# 组件挂载流程

  • constructor () 一般都省略
    constructor 主要是用来初始化 state 以及为事件函数绑定实例 (处理事件函数的 this 指向,让其指向组件本身)
    如果 state 在外边定义了,就不要在 constructor 中定义了
    constructor 内禁止使用 setState

  • componentWillMount () 组件将要被挂载的钩子

  • render () 组件中必须使用的
    render 是在初始化的时候和状态更新之后调用
    根据 state 和 props 渲染 react 节点 (虚拟节点),发生变化重新渲染
    且需要是纯函数(里面不要写异步操作)

  • componentDidMount() 组件 dom 渲染完毕时调用 ,可以发送网络请求使用 setState 更新数据,获取真实 dom 节点

# 组件卸载流程

  • componentWillUnmount () 组件即将被销毁 ,做解绑事件,清除定时器等
  • ReactDom.unmountComponentAtNode (document.getElementById(‘test’)) 卸载组件的方法

# setState 流程

  • shouldComponentUpdate () 组件是否应该更新, 通过 return true/false 来控制组件是否更新 (控制组件更新的阀门)
    如果不写这个钩子,React 会自动返回 true,如果自己写了这个钩子,则必须返回一个 true/false

  • componentWillUpdate () 组件将要更新

  • render () 根据 state 和 props 渲染 react 节点,发生变化重新渲染

  • componentDidUpdate() 组件更新 dom 渲染完毕,可以发送网络请求使用 setState 更新数据,获取更新后的真实的 dom 节点

# forceUpdate 流程

强制更新 ,可以不对状态进行任何的修改,组件也能进行更新

  • componentWillUpdate () 组件将要更新

  • render () 根据 state 和 props 渲染 react 节点,发生变化重新渲染

  • componentDidUpdate () 组件更新 dom 渲染完毕,可以发送网络请求使用 setState 更新数据,获取更新后的真实的 dom 节点

<script type="text/babel">
  class Count extends React.Component {
    //强制更新按钮的回调
    force = () => {
      this.forceUpdate();
    };
    // 会执行 componentWillUpdate,render,componentDidUpdate 这三个生命周期钩子

    render() {
      return (
        <button onClick={this.force}>
          不更改任何状态中的数据,强制更新一下
        </button>
      );
    }
  }
  //渲染组件
  ReactDOM.render(<Count />, document.getElementById("test"));
</script>

# 父组件 render 流程

  • componentWillReceiveProps () 第一次传的不算,组件将要接收新的 props

  • shouldComponentUpdate () 组件是否应该更新, 通过 return true/false 来控制组件是否更新 (控制组件更新的阀门)
    如果不写这个钩子,React 会自动返回 true,如果自己写了这个钩子,则必须返回一个 true/false

  • componentWillUpdate () 组件将要更新

  • render () 根据 state 和 props 渲染 react 节点,发生变化重新渲染

  • componentDidUpdate () 组件更新 dom 渲染完毕,可以发送网络请求使用 setState 更新数据,获取更新后的真实的 dom 节点

<script type="text/babel">
  //父组件A
  class A extends React.Component {
    //初始化状态
    state = { carName: "奔驰" };
    changeCar = () => {
      this.setState({ carName: "奥拓" });
    };
    render() {
      return (
        <div>
          <div>我是A组件</div>
          <button onClick={this.changeCar}>换车</button>
          <B carName={this.state.carName} />
        </div>
      );
    }
  }

  //子组件B
  class B extends React.Component {
    //组件将要接收新的props的钩子
    componentWillReceiveProps(props) {
      console.log("B---componentWillReceiveProps", props);
    }
    render() {
      return <div>我是B组件,接收到的车是:{this.props.carName}</div>;
    }
  }
  ReactDOM.render(<A />, document.getElementById("test"));
</script>

# 生命周期 (旧) 总结

  • 初始化阶段:由 ReactDOM.render () 触发 — 初次渲染

    • constructor()
    • componentWillMount()
    • render()
    • componentDidMount() =====> 常用,组件 dom 渲染完毕时调用,可以发送网络请求,打开定时器,订阅消息,使用 setState 更新数据,获取真实 dom 节点
  • 更新阶段:由组件内部 this.setSate () 或父组件 render 触发

    • shouldComponentUpdate (nextProps,nextState) 接受两个参数,新的 props 和新的 state。组件是否应该可以更新,需要返回一个布尔值,用的不多
    • componentWillUpdate()
    • render() =====> 必须使用的一个,初始化渲染或更新渲染调用
    • componentDidUpdate (prevProps,prevState,snapshot): 以前的 props 和 state,以及 getSnapshotBeforeUpdate () 传递下来的快照值
  • 卸载组件:由 ReactDOM.unmountComponentAtNode () 触发

    • componentWillUnmount() =====> 常用,组件即将被销毁,做解绑事件,清除定时器,取消订阅消息等

# 生命周期 (新)

  • 除了 componentWillUnmount 之外,所有带 will 的都需要加上 UNSAFE_ 前缀,因为新版本里 这三个 (componentWillMount,componentWillReceiveProps,componentWillUpdate) 即将被弃用
  • 新加了两个 生命周期函数,但是 不常用 ( static getDerivedStateFromProps() , getSnapshotBeforeUpdate ) 这两个基本用不到
  • static getDerivedStateFromProps(props,state) :从 props 那里得到一个派生的状态。第二个参数 state 会将定义好的 state 传过来。 state 的值在任何时候都取决于 props 的时候 (包括后期的更新也取决于 props) 就用它,仅适用于罕见的用例, 开发中用不到 ,这个东西会使代码冗余,并使组件难以维护
  • getSnapshotBeforeUpdate(prevProps, prevState, snapshot) : 基本不用 ,在最近一次渲染输出 (提交到 DOM 节点) 之前调用,它使得组件能在发生更改之前从 DOM 中捕获一些信息 (例如:滚动位置) 可以在更新之前获取一些信息 ,个人理解这个生命函数就是在更新之前做的一些事情

# getSnapshotBeforeUpdate 案例

此案例效果:在动态加载 li 的情况下,鼠标放在列表的哪里就固定在哪里

<style>
  .list {
    width: 200px;
    height: 150px;
    background-color: skyblue;
    overflow: auto;
  }
  .news {
    height: 30px;
  }
</style>
<script type="text/babel">
  class NewsList extends React.Component {
    state = { newsArr: [] };

    componentDidMount() {
      setInterval(() => {
        //获取原状态
        const { newsArr } = this.state;
        //模拟一条新闻
        const news = "新闻" + (newsArr.length + 1);
        //更新状态
        this.setState({ newsArr: [news, ...newsArr] });
      }, 1000);
    }

    getSnapshotBeforeUpdate() {
      return this.refs.list.scrollHeight;
    }

    componentDidUpdate(preProps, preState, height) {
      this.refs.list.scrollTop += this.refs.list.scrollHeight - height;
    }

    render() {
      return (
        <div className="list" ref="list">
          {this.state.newsArr.map((n, index) => {
            return (
              <div key={index} className="news">
                {n}
              </div>
            );
          })}
        </div>
      );
    }
  }
  ReactDOM.render(<NewsList />, document.getElementById("test"));
</script>

# 生命周期 (新) 总结

  • 初始化阶段:由 ReactDOM.render () 触发 — 初次渲染

    • constructor()
    • getDerivedStateFromProps
    • render()
    • componentDidMount() =====> 常用,组件 dom 渲染完毕时调用,可以发送网络请求,打开定时器,订阅消息,使用 setState 更新数据,获取真实 dom 节点
  • 更新阶段:由组件内部 this.setSate () 或父组件重新 render 触发

    • getDerivedStateFromProps
    • shouldComponentUpdate()
    • render() =====> 必须使用的一个,初始化渲染或更新渲染调用
    • getSnapshotBeforeUpdate
    • componentDidUpdate()
  • 卸载组件:由 ReactDOM.unmountComponentAtNode () 触发

    • componentWillUnmount() =====> 常用,组件即将被销毁,做解绑事件,清除定时器,取消订阅消息等

# 虚拟 DOM 的 diffing 算法

# 概念

会根据虚拟 DOM 创建出一棵树,如果虚拟 DOM 树没有变化则真实树放置不动。如果虚拟 DOM 树改变则真实树也会改变。

虚拟 DOM 的最小单位是节点 (标签),且会逐层对比

# key 值的作用

  • 简单的说: key 是虚拟 DOM 对象的 标识 ,在更新显示时 key 起着 极其重要的作用 。

  • 详细的说: 当状态中的数据发生变化时,react 会根据【新数据】生成【新的虚拟 DOM】,
    随后 React 进行【新虚拟 DOM】与【旧虚拟 DOM】的 diff 比较,比较规则如下:

    • 旧虚拟 DOM 中 找到了 与新虚拟 DOM 相同的 key :
      (1). 若虚拟 DOM 中内容 没变 ,直接使用之前的真实 DOM
      (2). 若虚拟 DOM 中内容 变了 ,则生成新的真实 DOM,随后替换掉页面中之前的真实 DOM

    • 旧虚拟 DOM 中 未找到 与新虚拟 DOM 相同的 key
      根据数据创建新的真实 DOM,随后渲染到到页面

  • 用 index 作为 key 可能会引发的问题:

    • 使用 index 作为 key 值,虚拟 dom 对比就是顺序对比,如果在数组前面插入元素,它需要对比整个数组,用 id 属性,只需要一次
    • 若对数据进行逆序添加、逆序删除等破坏性操作:会产生没有必要的真实 DOM 更新, 会导致严重的效率问题
    • 如果结构中还包含输入类的 DOM:会产生错误 DOM 更新 (界面有问题)
  • 开发中如何选择 key?:

    • 最好使用每条数据的 唯一标识 作为 key, 比如 id、手机号、身份证号、学号等唯一值。
    • 如果确定只是简单的展示数据,用 index 也是可以的。

# React 脚手架

VSCode 下载 React 插件: 以 ES7 开头那个
npm 已经内置了 react 的脚手架工具 (脚手架:用于搭建环境的工具),要求 Node >= 8.10 和 npm >= 5.6

  • 安装脚手架 :npm i create-react-app -g
  • npx create-react-app 项目名 创建新的 React 项目
  • npm start 运行
  • 在组件中输标签不能自动补去设置中,搜索 react,添加项 javascript,值为 javascriptreact
  • 或去文件 - 首选项 - 设置中提示的配置文件 (setting.json) 中输入,要保证最后一项没有逗号
    “emmet.includeLanguages”: {
    “javascript”: “javascriptreact”,
    “wxml”: “html”
    },

# public 静态资源文件夹

  • public 下的 index.html

    <!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" />
        <!-- 用于配置浏览器页面+地址栏的颜色(仅支持安卓手机浏览器) -->
        <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" />
        <!-- 应用加壳时的配置文件 -->
        <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
        <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 源码文件夹

  • App.js 对应 vue 中 App.vue
  • index.js 对应 vue 中的 main.js, 入口文件
  • index.css 是全局样式
  • reportWebVitals.js 页面性能分析文件
  • setupTests.js 组件单元测试的文件

# Hello 案例

可以安装一个 VSCode 插件:ES7+ React/Redux/React-Native snippets,方便开发

  • App.js

    <!-- 这里的Component是ES6语法的导出,而不是解构赋值 -->
    import React, { Component } from "react";
    import Hello from "./components/Hello/Hello";
    import Welcome from "./components/Welcome/Welcome";
    /*
      用{}可以引入分别暴露和统一暴露的东西,这里是ES6的另外一种引入方式,不是解构赋值。
      如果有这种引用方式,说明 from 后边的文件里边用了多种暴露的形式
    */
    
    // 创建并暴露App组件
    export default class App extends Component {
      render() {
        return (
          <div>
            <Hello />
            <Welcome />
          </div>
        );
      }
    }
  • index.js

    import React from "react";
    import ReactDOM from "react-dom";
    import App from "./App";
    ReactDOM.render(<App />, document.getElementById("root"));
  • Hello.jsx

    注意这里的后缀是 .jsx ,主要是为了和其他的 js 文件区分, jsx 用来表示组件

    import React, { Component } from "react";
    import "./Hello.css";
    export default class Hello extends Component {
      render() {
        return <h1 className="title">Hello</h1>;
      }
    }
  • Welcome.jsx

    import React, { Component } from "react";
    // 样式的模块化
    // 如果两个组件中有同名的属性,则后边的覆盖前边的,使用了模块化之后就可以解决这个问题
    // 对应 Vue 中的 scoped
    import welcome from "./Welcome.module.css";
    export default class Welcome extends Component {
      render() {
        return <h1 className={welcome.title}>Welcome</h1>;
      }
    }

# TodoList–props 案例

  • List.jsx

    export default class List extends Component {
      render() {
        const { todos } = this.props;
        return (
          <ul className="todo-main">
            {todos.map((todo) => {
              return (
                <Item
                  key={todo.id}
                  // 此处是将todo整体作为props传递给Item组件
                  {...todo}
                />
              );
            })}
          </ul>
        );
      }
    }
  • Item.jsx

    export default class Item extends Component {
      render() {
        const { name, done } = this.props;
        return (
          <li>
            <label>
            <!-- 只管第一次上来勾还是不勾,以后想改也可以改,但是可能会有小 bug(defaultChecked只在第一次执行的时候有作用,之后就没作用了,需要给checkbox指定onChange事件),checked 就是一下子写死了 -->
              <input type="checkbox" defaultChecked={done} />
              <span>{name}</span>
            </label>
            <button>删除</button>
          </li>
        );
      }
    }

# TodoList 案例

# todoList 案例相关知识点

  • 拆分组件、实现静态组件,注意: className 、 style 的写法
  • 动态初始化列表,如何确定将数据放在哪个组件的 state 中?
    • 某个组件使用:放在其自身的 state 中
    • 某些组件使用:放在他们共同的父组件 state 中(官方称此操作为:状态提升)
  • 关于父子之间通信:
    • 【父组件】给【子组件】传递数据:通过 props 传递
    • 【子组件】给【父组件】传递数据:通过 props 传递,要求父提前给子传递一个函数
  • 注意 defaultChecked 和 checked 的区别,类似的还有:defaultValue 和 value
  • 状态在哪里,操作状态的方法就在哪里
  • reduce 用处 :数组统计,条件求和,筛选最值

# 案例源码

  • App.jsx

    import React, { Component } from "react";
    import Header from "./components/Header";
    import List from "./components/List";
    import Footer from "./components/Footer";
    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>
        );
      }
    }
  • Header.jsx

    import React, { Component } from "react";
    import PropTypes from "prop-types";
    import { nanoid } from "nanoid";
    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>
        );
      }
    }
  • List.jsx

    import React, { Component } from "react";
    import PropTypes from "prop-types";
    import Item from "../Item";
    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>
        );
      }
    }
  • Item.jsx

    import React, { Component } from "react";
    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>
        );
      }
    }
  • Footer.jsx

    import React, { Component } from "react";
    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>
        );
      }
    }

文章作者: Mrr-cxh
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Mrr-cxh !
赏
 上一篇
React 3.0 React 3.0
React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库。使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作“组件”。
2022-05-12
下一篇 
React 1.0 React 1.0
React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库。使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作“组件”。
2021-09-11
  目录