1. 什么是React? #

2. 组件化的概念 #

3.搭建React开发环境 #

cnpm i create-react-app -g
create-react-app react2019
cd react2019
yarn start

4.JSX #

4.1 什么是JSX #

ReactDOM.render(
  <h1>Hello</h1>,
  document.getElementById('root')
);

4.2 什么是元素 #

<h1 className="title" style={{color:'red'}}>hello</h1>
React.createElement("h1", {
  className: "title",
  style: {
    color: 'red'
  }
}, "hello");

createElement的结果

{
  type:'h1',
  props:{
    className: "title",
    style: {
      color: 'red'
    }
  },
  children:"hello"
}

4.3 JSX表达式 #

4.4 JSX属性 #

ReactDOM.render(
  <h1 className="title" style={{color:'red'}}>Hello</h1>,
  document.getElementById('root')
);

4.5 JSX也是对象 #

if中使用

import React from 'react';
import ReactDOM from 'react-dom';
function greeting(name) {
    if (name) {
      return <h1>Hello, {name}!</h1>;
    }
    return <h1>Hello, Stranger.</h1>;
  }
  let name = 'zhufeng';
  const element = greeting(name);

  ReactDOM.render(
    element,
    document.getElementById('root')
  );

for中使用

import React from 'react';
import ReactDOM from 'react-dom';
let names = ['张三','李四','王五'];
let elements = [];
for(let i=0;i<names.length;i++){
  elements.push(<li>{names[i]}</li>);
}
ReactDOM.render(
    <ul>
      {elements}
    </ul>,
    document.getElementById('root')
);

4.6 更新元素渲染 #

import React from 'react';
import ReactDOM from 'react-dom';
function tick() {
    const element = (
      <div>
        {new Date().toLocaleTimeString()}
      </div>
    );
    ReactDOM.render(element, document.getElementById('root'));
}
setInterval(tick, 1000);

4.7 React只会更新必要的部分 #

5. 组件 & Props #

5.1 函数(定义的)组件 #

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

5.2 类(定义的)组件 #

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

5.3 组件渲染 #

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="zhufengjiagou" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);

5.4 复合组件&提取组件 #

class Panel extends Component{
    render(){
        let {header,body} = this.props;
        return (
            <div className="container">
                <div className="panel-default panel">
                    <Header header={header}></Header>
                    <Body body={body}/>
                </div>
            </div>
        )
    }
}
class Body extends Component{
    render(){return (<div className="panel-body">{this.props.body}</div>)}
}
class Header extends Component{
    render(){return (<div className="panel-heading">{this.props.header}</div>)}
}
let data = {header:'zhufeng',body:'jiagou'};
ReactDOM.render(<Panel {...data}/>,window.root);

5.5 Props的只读性 #

//纯函数
function sum(a, b) {
  return a + b;
}
//非纯函数
function withdraw(account, amount) {
  account.total -= amount;
}

5.6 进类型检查 #

import PropTypes from 'prop-types';

MyComponent.propTypes = {
  // 你可以将属性声明为 JS 原生类型,默认情况下
  // 这些属性都是可选的。
  optionalArray: PropTypes.array,
  optionalBool: PropTypes.bool,
  optionalFunc: PropTypes.func,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalString: PropTypes.string,
  optionalSymbol: PropTypes.symbol,

  // 任何可被渲染的元素(包括数字、字符串、元素或数组)
  // (或 Fragment) 也包含这些类型。
  optionalNode: PropTypes.node,

  // 一个 React 元素。
  optionalElement: PropTypes.element,

  // 你也可以声明 prop 为类的实例,这里使用
  // JS 的 instanceof 操作符。
  optionalMessage: PropTypes.instanceOf(Message),

  // 你可以让你的 prop 只能是特定的值,指定它为
  // 枚举类型。
  optionalEnum: PropTypes.oneOf(['News', 'Photos']),

  // 一个对象可以是几种类型中的任意一个类型
  optionalUnion: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.instanceOf(Message)
  ]),

  // 可以指定一个数组由某一类型的元素组成
  optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

  // 可以指定一个对象由某一类型的值组成
  optionalObjectOf: PropTypes.objectOf(PropTypes.number),

  // 可以指定一个对象由特定的类型值组成
  optionalObjectWithShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number
  }),

  // 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
  // 这个 prop 没有被提供时,会打印警告信息。
  requiredFunc: PropTypes.func.isRequired,

  // 任意类型的数据
  requiredAny: PropTypes.any.isRequired,

  // 你可以指定一个自定义验证器。它在验证失败时应返回一个 Error 对象。
  // 请不要使用 `console.warn` 或抛出异常,因为这在 `onOfType` 中不会起作用。
  customProp: function(props, propName, componentName) {
    if (!/matchme/.test(props[propName])) {
      return new Error(
        'Invalid prop `' + propName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  },

  // 你也可以提供一个自定义的 `arrayOf` 或 `objectOf` 验证器。
  // 它应该在验证失败时返回一个 Error 对象。
  // 验证器将验证数组或对象中的每个值。验证器的前两个参数
  // 第一个是数组或对象本身
  // 第二个是他们当前的键。
  customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
    if (!/matchme/.test(propValue[key])) {
      return new Error(
        'Invalid prop `' + propFullName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  })
};
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
class Person extends React.Component{
  static defaultProps = {
    name:'Stranger'
  }
    static propTypes={
        name: PropTypes.string.isRequired,
        age: PropTypes.number.isRequired,
        gender: PropTypes.oneOf(['male','famale']),
        hobby: PropTypes.array,
        postion: PropTypes.shape({
            x: PropTypes.number,
            y:PropTypes.number
        }),
        age(props,propName,componentName) {
            let age=props[propName];
            if (age <0 || age>120) {
                return new Error(`Invalid Prop ${propName} supplied to ${componentName}`)
            }
        }
    }
    render() {
        let {name,age,gender,hobby,position}=this.props;
        return (
            <table>
                <thead>
                <tr>
                    <td>姓名</td>
                    <td>年龄</td>
                    <td>性别</td>
                    <td>爱好</td>
                    <td>位置</td>
                </tr>
                </thead>
                <tbody>
                <tr>
                    <td>{name}</td>
                    <td>{age}</td>
                    <td>{gender}</td>
                    <td>{hobby.join(',')}</td>
                    <td>{position.x+' '+position.y}</td>
                </tr>
                </tbody>
            </table>
        )
    }
}
let person={
    age: 100,
    gender:'male',
    hobby: ['basketball','football'],
    position: {x: 10,y: 10},
}
ReactDOM.render(<Person {...person}/>, document.getElementById('root'));

6. 虚拟DOM #

4.1 index.js #

import React from './react';
import ReactDOM from './react-dom';
//let element = <h1 className="title" style={{color:'red',fontSize:'24px'}}></h1>
//let element = React.createElement('h1',{className:'title',style:{color:'red',fontSize:'50px'}},'hello');
//console.log(JSON.stringify(element));
//function Welcome(props){
//    return React.createElement('h1',{className:'title'},props.title);
//}
class Welcome extends React.Component{
    render(){
        return React.createElement('h1',{className:'title'},this.props.title);
    }
}
let element = React.createElement(Welcome,{title:'标题'});
ReactDOM.render(element, document.getElementById('root'));

4.2 react.js #

import createElement from './element';
class Component{
    static isReactComponent = true
    constructor(props){
      this.props = props;
    }
  }
export default {
    createElement,Component
}

4.3 element.js #

const ReactElement = function(type,props) {
    const element = {
        type: type,
        props: props,
    };
    return element;
}

function createElement(type,config,children){
  let propName;
  const props = {};
  for (propName in config) {
    props[propName] = config[propName];
  }
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    props.children = Array.prototype.slice.call(arguments,2);
  }
  return ReactElement(type,props);
}
export default createElement;

4.4 react-dom.js #

function render(element,container){
    if(typeof element == 'string'){
      return container.appendChild(document.createTextNode(element))
    }
    let type,props;
    type = element.type;  
    props = element.props; 
    if(type.isReactComponent){//如果为true说明它是一个类组件
      element = new type(props).render();
      type = element.type;  
      props = element.props;
    }else  if(typeof type =='function'){
      element = type(props);
      type = element.type;  
      props = element.props;
    }
    let domElement = document.createElement(type);
    for(let propName in props){
        if(propName === 'children'){
          let children = props[propName];
          children = Array.isArray(children)?children:[children];
          children.forEach(child=>render(child,domElement));
        }else if(propName === 'className'){
          domElement.className = props[propName];
        }else if(propName === 'style'){
          let styleObj = props[propName];
          /**
          for(let attr in styleObj){
              domElement.style[attr] =  styleObj[attr];
          }
           */
          let cssText = Object.keys(styleObj).map(attr=>{
            return `${attr.replace(/([A-Z])/g,function(){ return"-"+arguments[1]})}:${styleObj[attr]}`;
          }).join(';');
          domElement.style.cssText = cssText;
        }else{
          domElement.setAttribute(propName,props[propName]);
        }
    }
    container.appendChild(domElement);
  }
export default {render};

6. 状态 #

import React from 'react';
import ReactDOM from 'react-dom';
class Clock extends React.Component {
    constructor(props) {
      super(props);
      this.state = {date: new Date()};
    }

    componentDidMount() {
      this.timerID = setInterval(
        () => this.tick(),
        1000
      );
    }

    componentWillUnmount() {
      clearInterval(this.timerID);
    }

    tick() {
      this.setState({
        date: new Date()
      });
    }

    render() {
      return (
        <div>
          <h1>Hello, world!</h1>
          <h2>It is {this.state.date.toLocaleTimeString()}</h2>
        </div>
      );
    }
  }

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

6.1 不要直接修改 State #

import React from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            number: 0
        };
    }

    componentDidMount() {
        this.timerID = setInterval(
            () => {
                //this.setState({number:this.state.number+1});
                this.state.number = this.state.number + 1;
            },
            1000
        );
    }

    componentWillUnmount() {
        clearInterval(this.timerID);
    }

    render() {
        return (
            <div >
                <p> {this.state.number} </p>
            </div>
        );
    }
}

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

6.2 State 的更新可能是异步的 #

import React from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            number: 0
        };
    }
    handleClick = ()=>{
        //this.setState({number:this.state.number+1});
        //console.log(this.state.number);
        //this.setState({number:this.state.number+1});

        this.setState((state)=>(
            {number:state.number+1}
        ));
        this.setState((state)=>(
            {number:state.number+1}
        ));
    }
    render() {
        return (
            <div >
                <p> {this.state.number} </p>
                <button onClick={this.handleClick}>+</button>
            </div>
        );
    }
}

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

6.3 State 的更新会被合并 #

import React from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            name:'zhufeng',
            number: 0
        };
    }
    handleClick = ()=>{
        //this.setState({number:this.state.number+1});
        //console.log(this.state.number);
        //this.setState({number:this.state.number+1});

        this.setState((state)=>(
            {number:state.number+1}
        ));
        this.setState((state)=>(
            {number:state.number+1}
        ));
    }
    render() {
        return (
            <div >
                <p>{this.state.name}: {this.state.number} </p>
                <button onClick={this.handleClick}>+</button>
            </div>
        );
    }
}

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

6.4 数据是向下流动的 #

import React from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            name:'zhufeng',
            number: 0
        };
    }
    handleClick = ()=>{
        this.setState((state)=>(
            {number:state.number+1}
        ));
    }
    render() {
        return (
            <div style={{border:'1px solid red'}}>
                <p>{this.state.name}: {this.state.number} </p>
                <button onClick={this.handleClick}>+</button>
                <SubCounter number={this.state.number}/>
            </div>
        );
    }
}
class SubCounter extends React.Component {
  render(){
      return <div style={{border:'1px solid blue'}}>子计数器:{this.props.number}</div>;
  }
}
ReactDOM.render(
    <Counter />,
    document.getElementById('root')
);

7. 事件处理 #

import React from 'react';
import ReactDOM from 'react-dom';
class Link extends React.Component {
    handleClick(e) {
        e.preventDefault();
        console.log('The link was clicked.');
    }

    render() {
        return (
            <a href="http://www.baidu.com" onClick={this.handleClick}>
                Click me
          </a>
        );
    }
}

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

7.2 this #

class LoggingButton extends React.Component {
    handleClick() {
        console.log('this is:', this);
    }
    handleClick1 = () => {
        console.log('this is:', this);
    }
    render() {
        //onClick={this.handleClick.bind(this)
        return (
            <button onClick={(event) => this.handleClick(event)}>
                Click me
        </button>
        );
    }
}

7.3 向事件处理程序传递参数 #

class LoggingButton extends React.Component {
    handleClick1 = (id,event) => {
        console.log('id:', id);
    }
    render() {
        return (
           <>
            <button onClick={(event) => this.handleClick('1',event)}>
                Click me
            </button>
            <button onClick={this.handleClick.bind(this,'1')}>
                Click me
            </button>
           </>
        );
    }
}

7.4 Ref #

7.4.1 ref的值是一个字符串 #

class Sum extends React.Component {
    handleAdd = (event) => {
        let a = this.refs.a.value;
        let b = this.refs.b.value;
        this.refs.c.value = a+b;
    }

    render() {
        return (
            <>
                <input ref="a" />+<input ref="b"/><button onClick={this.handleAdd}>=</button><input ref="c"/>
            </>
        );
    }
}

7.4.2 ref的值是一个函数 #

class Sum extends React.Component {
    handleAdd = (event) => {
        let a = this.a.value;
        let b = this.b.value;
        this.result.value = a+b;
    }
    render() {
        return (
            <>
                <input ref={ref=>this.a= ref} />+<input ref={ref=>this.b= ref} /><button onClick={this.handleAdd}>=</button><input ref={ref=>this.result= ref} />
            </>
        );
    }
}

7.4.3 为 DOM 元素添加 ref #

class Sum extends React.Component {
    constructor(props){
        super(props);
        this.a = React.createRef();
        this.b = React.createRef();
        this.result = React.createRef();
    }
    handleAdd = () => {
        let a = this.a.current.value;
        let b = this.b.current.value;
        this.result.current.value = a+b;
    }
    render() {
        return (
            <>
                <input ref={this.a} />+<input ref={this.b} /><button onClick={this.handleAdd}>=</button><input ref={this.result} />
            </>
        );
    }
}

7.4.4 为 class 组件添加 Ref #

class Form extends React.Component {
    constructor(props){
        super(props);
        this.input = React.createRef();
    }
    getFocus = () => {
        this.input.current.getFocus();
    }
    render() {
        return (
            <>
              <TextInput ref={this.input}/>
              <button onClick={this.getFocus}>获得焦点</button>
            </>
        );
    }
}
class TextInput extends React.Component{
    constructor(props){
        super(props);
        this.input = React.createRef();
    }
    getFocus =()=>{
        this.input.current.focus();
    }
    render(){
        return <input ref={this.input}/>
    }
}

7.4.5 Ref转发 #

使用forwardRef

class Form extends React.Component {
    constructor(props){
        super(props);
        this.input = React.createRef();
    }
    getFocus = () => {
        this.input.current.focus();
    }
    render() {
        return (
            <>
              <TextInput ref={this.input}/>
              <button onClick={this.getFocus}>获得焦点</button>
            </>
        );
    }
}

const TextInput = React.forwardRef((props,ref)=>(
    <input ref={ref}/>
));

实现

function createRef(){
    return {
        current:null
    }
}
class Form extends React.Component {
    constructor(props){
        super(props);
        this.input = createRef();
    }
    getFocus = () => {
        this.input.current.focus();
    }
    render() {
        return (
            <>
              <TextInput myref={this.input}/>
              <button onClick={this.getFocus}>获得焦点</button>
            </>
        );
    }
}
function forwardRef(funcComponent){
    return function(props){
        let ref = props.myref;
        return funcComponent(props,ref);
    }
}
const TextInput = forwardRef((props,ref)=>(
    <input ref={ref}/>
));

8.生命周期 #

8.1 旧版生命周期 #

react15

import React, {
    Component,
    useState,
    useImperativeHandle,
    useCallback,
    useMemo,
    useRef,
    useEffect,
    forwardRef,
    useLayoutEffect
} from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component{ // 他会比较两个状态相等就不会刷新视图 PureComponent是浅比较
    static defaultProps = {
      name:'珠峰培训'
    };
    constructor(props){
      super();
      this.state = {number:0}
      console.log('1.constructor构造函数')
    }
    componentWillMount(){ // 取本地的数据 同步的方式:采用渲染之前获取数据,只渲染一次
      console.log('2.组件将要加载 componentWillMount');
    }
    componentDidMount(){
      console.log('4.组件挂载完成 componentDidMount');
    }
    handleClick=()=>{
      this.setState({number:this.state.number+1});
    };
    // react可以shouldComponentUpdate方法中优化 PureComponent 可以帮我们做这件事
    shouldComponentUpdate(nextProps,nextState){ // 代表的是下一次的属性 和 下一次的状态
      console.log('5.组件是否更新 shouldComponentUpdate');
      return nextState.number%2;
      // return nextState.number!==this.state.number; //如果此函数种返回了false 就不会调用render方法了
    } //不要随便用setState 可能会死循环
    componentWillUpdate(){
      console.log('6.组件将要更新 componentWillUpdate');
    }
    componentDidUpdate(){
      console.log('7.组件完成更新 componentDidUpdate');
    }
    render(){
      console.log('3.render');
      return (
        <div>
          <p>{this.state.number}</p>
          {this.state.number>3?null:<ChildCounter n={this.state.number}/>}
          <button onClick={this.handleClick}>+</button>
        </div>
      )
    }
  }
  class ChildCounter extends Component{
    componentWillUnmount(){
      console.log('组件将要卸载componentWillUnmount')
    }
    componentWillMount(){
      console.log('child componentWillMount')
    }
    render(){
      console.log('child-render')
      return (<div>
        {this.props.n}
      </div>)
    }
    componentDidMount(){
      console.log('child componentDidMount')
    }
    componentWillReceiveProps(newProps){ // 第一次不会执行,之后属性更新时才会执行
      console.log('child componentWillReceiveProps')
    }
    shouldComponentUpdate(nextProps,nextState){
      return nextProps.n%3==0; //子组件判断接收的属性 是否满足更新条件 为true则更新
    }
  }
ReactDOM.render(<Counter/>, document.getElementById('root'));

// defaultProps
// constructor
// componentWillMount
// render
// componentDidMount
// 状态更新会触发的
// shouldComponentUpdate nextProps,nextState=>boolean
// componentWillUpdate
// componentDidUpdate
// 属性更新
// componentWillReceiveProps newProps
// 卸载
// componentWillUnmount

8.2 新版生命周期 #

react16

8.2.1 getDerivedStateFromProps #

import React from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component{ 
    static defaultProps = {
      name:'珠峰架构'
    };
    constructor(props){
      super();
      this.state = {number:0}
    }

    handleClick=()=>{
      this.setState({number:this.state.number+1});
    };

    render(){
      console.log('3.render');
      return (
        <div>
          <p>{this.state.number}</p>
          <ChildCounter number={this.state.number}/>
          <button onClick={this.handleClick}>+</button>
        </div>
      )
    }
  }
  class ChildCounter extends React.Component{
      constructor(){
          super();
          this.state = {number:0};
      }
    static getDerivedStateFromProps(nextProps, prevState) {
        const {number} = nextProps;
        // 当传入的type发生变化的时候,更新state
        if (number%2==0) {
            return { number:number*2};
        }else{
            return { number:number*3};
        }
        // 否则,对于state不进行任何操作
        return null;
    }
    render(){
      console.log('child-render',this.state)
      return (<div>
        {this.state.number}
      </div>)
    }

  }

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

8.2.2 getSnapshotBeforeUpdate #

import React from 'react';
import ReactDOM from 'react-dom';
class ScrollingList extends React.Component {
    constructor(props) {
        super(props);
        this.state = {messages: []}
        this.wrapper = React.createRef();
    }

    addMessage() {
        this.setState(state => ({
            messages: [`${state.messages.length}`, ...state.messages],
        }))
    }
    componentDidMount() {
        this.timeID = window.setInterval(() => {//设置定时器
            this.addMessage();
        }, 1000)
    }
    componentWillUnmount() {//清除定时器
        window.clearInterval(this.timeID);
    }
    getSnapshotBeforeUpdate() {//很关键的,我们获取当前rootNode的scrollHeight,传到componentDidUpdate 的参数perScrollHeight
        return this.wrapper.current.scrollHeight;
    }
    componentDidUpdate(perProps, perState, prevScrollHeight) {
        const curScrollTop = this.wrapper.current.scrollTop;//当前向上卷去的高度
        //当前向上卷去的高度加上增加的内容高度
        this.wrapper.current.scrollTop = curScrollTop + (this.wrapper.current.scrollHeight - prevScrollHeight);
    }
    render() {
        let style = {
            height: '100px',
            width: '200px',
            border: '1px solid red',
            overflow: 'auto'
        }
        return (
            <div style={style} ref={this.wrapper} >
                {this.state.messages.map((message, index) => (
                    <div key={index}>{message} </div>
                ))}
            </div>
        );
    }
}

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

9. 条件渲染 #

9.1 条件渲染 #

9.2 列表&Key #

9.3 表单受控组件和非受控组件 #

9.4 todoApp #

import React from 'react';
import ReactDOM from 'react-dom';
import { declareTypeAlias } from '@babel/types';
class Todos extends React.Component {
   constructor(props){
       super(props);
       this.state = {
           text:'',
           todos:[]
       }
   }
   handleAdd = (event)=>{
    event.preventDefault();
    this.setState({
        todos:[...this.state.todos,{id:Date.now(),text:this.state.text}],
        text:''
    });
   }
   handleDelete(id){
    this.setState({
        todos:this.state.todos.filter(todo=>todo.id != id)
    });
   }
   handleChange = (event)=>{
    this.setState({
        text:event.target.value
    });
   }

   render() {
       let todos = this.state.todos.map(todo=><li key={todo.id}>{todo.text}<button onClick={()=>this.handleDelete(todo.id)}>X</button></li>);
        return (
           <>
            {this.props.name&&<h1>{this.props.name}</h1>}
            <form onSubmit={this.handleAdd}>
                <input type="text" value={this.state.text} onChange={this.handleChange}/>
                <input type="submit"/>
            </form>
            <ul>
              { todos}
            </ul>
            {this.state.todos.length>0?`你现在${this.state.todos.length}件待办事项`:`你现在没有待办事项`}
           </>
        );
    }
}

ReactDOM.render(
    <Todos name="待办事项"/>,
    document.getElementById('root')
);

10.状态提升 #

import React from 'react';
import ReactDOM from 'react-dom';
class Todos extends React.Component {
   constructor(props){
       super(props);
       this.state = {
           text:'',
           todos:[]
       }
   }
   handleAdd = (event)=>{
    event.preventDefault();
    this.setState({
        todos:[...this.state.todos,{id:Date.now(),text:this.state.text}],
        text:''
    });
   }
   handleDelete = (id)=>{
    this.setState({
        todos:this.state.todos.filter(todo=>todo.id != id)
    });
   }
   handleChange = (event)=>{
    this.setState({
        text:event.target.value
    });
   }

   render() {
        return (
           <>
            {this.props.name&&<h1>{this.props.name}</h1>}
            <TodoHeader text={this.state.text}  handleAdd={this.handleAdd} handleChange={this.handleChange}/>    
            <TodoItems todos={this.state.todos} handleDelete={this.handleDelete}/>
            <TodoFooter todos={this.state.todos}/>
           </>
        );
    }
}
class TodoHeader extends React.Component{
    render(){
        return (
            <form onSubmit={this.props.handleAdd}>
                <input type="text" value={this.props.text} onChange={this.props.handleChange}/>
                <input type="submit"/>
            </form>
        )
    }
}
class TodoItems extends React.Component{
    render(){
        let todos = this.props.todos.map(todo=><li key={todo.id}>{todo.text}<button onClick={()=>this.props.handleDelete(todo.id)}>X</button></li>);
        return (
            <ul>
              { todos}
            </ul>
        )
    }
}
class TodoFooter extends React.Component{
    render(){
        return (
           <>{this.props.todos.length>0?`你现在${this.props.todos.length}件待办事项`:`你现在没有待办事项`}</>
        )
    }
}

ReactDOM.render(
    <Todos name="待办事项"/>,
    document.getElementById('root')
);

参考 #