React Hooks快速入门(前提:React基础)(1)——对比详解State及生命周期、Ref的dom操作、Hooks官网综合实例详细解析并分步骤实现(附详细案例源码解析过程及版本迭代过程)

1. React Hooks

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

1.1 React Hooks 优势

  • 简化组件逻辑
  • 复用状态逻辑
  • 无需使用类组件编写

1.2 React 常用 Hook

注意:所有的Hooks都是用use来声明的。

  • useState
    const [state, setState] = useState(initialState);
  • useEffect
    类组件
    componentDidMount、componentDidUpdate 和 componentWillUnmount
    需要清除的副作用
  • useRef
  • useContext
    context
    createContext、Provider、Consumer、contextType
  • useMemo
  • useCallback

1.3 Hook 使用规则

  • 只在最顶层使用 Hook
  • 只在 React 函数中调用 Hook
    • React 函数组件中
    • React Hook 中

1.4 自定义 Hook

通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。

考虑到在blog中不好体现代码更改的位置,小迪才用github托管代码,大家可以查看github,看到详细版本修改过程,搭配博客学习。

2. useState

const [state, setState] = useState(initialState);

reactstate使用

2.1 example01

useState 基本使用。

2.1.1 example01-1

回顾类式组件,添加一个state,并渲染到界面上。

code\project\study-hooks\react-app\src\hooks\state.js

import React,{Component} from "react";
 
class State extends Component{
    state = {
        name:'Leo',
        age:18
    }
 
    render(){
        let {name,age} = this.state;
        return(
            <div>
                姓名:{name},<br/>
                年龄: {age}
            </div>
        );
    }
}
 
export default State;

code\project\study-hooks\react-app\src\App.js

import React from 'react';
import State from './hooks/state';
 
function App() {
  return (
    <div className="App">
      <State/>
    </div>
  );
}
 
export default App;

image-20200708164234351

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.01-1
Branch: bhStudy-Hooks

commit description:as0.01-1-example01-1(react的state使用——添加一个state)

tag:as0.01-1

2.1.2 example01-2

更新类式组件state:

绑定事件,写触发函数但需要绑定this,这里用箭头函数避免这个问题。

import React,{Component} from "react";
 
class State extends Component{
    state = {
        name:'Leo',
        age:18
    }
 
    setAge=()=>{
        let {age} = this.state;
        this.setState(
            {
                age: ++age
            }
        )
    }
 
    render(){
        let {name,age} = this.state;
        return(
            <div>
                姓名:{name},<br/>
                年龄: {age},<br/>
                <button onClick={this.setAge}>长大一岁</button>
            </div>
        );
    }
}
 
export default State;

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.01-2
Branch: bhStudy-Hooks

commit description:as0.01-2-example01-2(react的state使用——更新state)

tag:as0.01-2

仔细观察其实类组件的state用起来还是比较麻烦的,简单办法就是接下来我们一起学习的hooks

2.1.3 example01-3

hooks只能用在function组件中。

之前我们也不能在function中使用state,因为是不能使用this相关的东西,但有了hooks之后,这个逻辑就可以畅通了。

我们引入useState,其实Hook就是函数。它接收的参数其实就是state的初始值。

import React,{Component,useState} from "react";
 
function State() {
    console.log(useState({
        name:"leo",
        age: 18
    }));
    return (<div>
        姓名:,<br />
        年龄:,<br />
        <button onClick={()=>{
        }}>长大一岁</button>
    </div>);
}
 
export default State;

useState的返回值是1个数组,第1个里存放的是state设置的对象数据,第2个里实际存放的函数就是setstate

image-20200708183012744

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.01-3
Branch: bhStudy-Hooks

commit description:as0.01-3-example01-3(react的state使用——Hooks中useState的返回值)

tag:as0.01-3

2.1.4 example01-4

解构useState的返回值,第一个参数是设置的内容,第二个参数是set方法,解构出的命名自定义即可。

注意:初始化的state是对象,setState的时候必须也是对象。

import React,{Component,useState} from "react";
 
function State() {
    const [state,setAge] = useState({
        name:"leo",
        age: 18
    });
    let {name,age} = state;
    return (<div>
        姓名:{name},<br />
        年龄:{age},<br />
        <button onClick={()=>{
            setAge({
                age: age + 1
            });
        }}>长大一岁</button>
    </div>);
}
 
export default State;

注意:类式组件的setState会进行合并对象,而hooks中的function组件是不会进行合并,只会进行覆盖的。

因此需要合并对象的话,先把原先的state进行展开,再去设置age,即我们自己去实现合并age

import React,{Component,useState} from "react";
 
function State() {
    const [state,setAge] = useState({
        name:"leo",
        age: 18
    });
    let {name,age} = state;
    return (<div>
        姓名:{name},<br />
        年龄:{age},<br />
        <button onClick={()=>{
            setAge({
                ...state,
                age: age + 1
            });
        }}>长大一岁</button>
    </div>);
}
 
export default State;

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.01-4
Branch: bhStudy-Hooks

commit description:as0.01-4-example01-4(react的state使用——Hooks中实现数据添加和更新)

tag:as0.01-4

2.1.5 example01-5

hooks并不推荐这么使用,因为本来hooks推荐的是单一逻辑或者简化逻辑。

如有一个状态,就应该单独定义1个userState的这个钩子,应该单独去维护,而不是合并在一起维护。这样会导致将来的业务逻辑和组件逻辑越来越复杂。

import React,{Component,useState} from "react";
 
function State() {
    const [name,setName] = useState("leo");
    const [age,setAge] = useState(18)
    return (<div>
        姓名:{name},<br />
        年龄:{age},<br />
        <button onClick={()=>{
            setAge(age+1);
        }}>长大一岁</button>
    </div>);
}
 
export default State;

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.01-5
Branch: bhStudy-Hooks

commit description:as0.01-5-example01-5(react的state使用——Hooks中实现数据添加和更新,维护逻辑单一)

tag:as0.01-5

3. useEffect

类组件
componentDidMount、componentDidUpdate 和 componentWillUnmount
需要清除的副作用

3.1 example02

hooks钩子对于生命周期的应用。

3.1.1 example02-1

基本框子:edit为真显示input,点击a标签edit为真后即可显示input

类式组件切换显示编辑框。

code\project\study-hooks\react-app\src\hooks\effect.js

import React,{Component,useState} from "react";
 
class Effect extends Component{
   state = {
        text: "这是今天的进度",
        edit: false  // 是否在编辑状态
   }
   setEditState=(edit)=>{
        this.setState({
            edit
        });
   }
 
   render(){
      let {text,edit} = this.state;
      return (<div>
            {edit?
                <input
                    type="text"
                />
                :
                <div >{text}  <a onClick={()=>{
                    this.setState({
                        edit: true
                    })
                }}>编辑</a>
                </div>
            }
      </div>);
  }
}
 
export default Effect;

code\project\study-hooks\react-app\src\App.js

import React from 'react';
import Effect from "./hooks/effect";
 
 
function App() {
  return (
    <div className="App">
      <Effect/>
    </div>
  );
}
 
export default App;
 

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.02-1
Branch: bhStudy-Hooks

commit description:as0.02-1-example02-1(react的生命周期useEffect使用——基本框子)

tag:as0.02-1

3.1.2 example02-2

input框里的内容与状态关联

   render(){
      let {text,edit} = this.state;
      return (<div>
            {edit?
                <input
                    type="text"
                    value={text}
                />
                :
                <div >{text}  <a onClick={()=>{
                    this.setState({
                        edit: true
                    })
                }}>编辑</a>
                </div>
            }
      </div>);
   }

提示输入框是非受控组件,React要求要实现为受控组件,因此必须设置onChange事件。

image-20200709091504102

render(){
      let {text,edit} = this.state;
      return (<div>
            {edit?
                <input
                    type="text"
                    value={text}
                    onChange={
                        (e) => {
                            this.setState({
                            text:e.target.value
                            })
                        }
                    }
                />
                :
                <div >{text}  <a onClick={()=>{
                    this.setState({
                        edit: true
                    })
                }}>编辑</a>
                </div>
            }
      </div>);
   }

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.02-2
Branch: bhStudy-Hooks

commit description:as0.02-2-example02-2(react的生命周期useEffect使用——input框里的内容与状态关联)

tag:as0.02-2

3.1.3 example02-3

实现失去焦点切换回文字状态(类式组件实现)

return (<div>
            {edit?
                <input
                    type="text"
                    value={text}
                    onChange={
                        (e) => {
                            this.setState({
                            text:e.target.value
                            })
                        }
                    }
                    onBlur={
                        ()=>{
                            this.setState({
                                edit:false
                            })
                        }
                    }
                />
                :
                <div >{text}  <a onClick={()=>{
                    this.setState({
                        edit: true
                    })
                }}>编辑</a>
                </div>
            }
      </div>);
   }

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.02-3
Branch: bhStudy-Hooks

commit description:as0.02-3-example02-3(react的生命周期useEffect使用——实现失去焦点切换回文字状态)

tag:as0.02-3

3.1.4 example02-4

生命周期函数——组件挂载完毕、更新完毕阶段

import React,{Component,useState} from "react";
 
class Effect extends Component{
   state = {
        text: "这是今天的进度",
        edit: false  // 是否显示编辑状态
   }
   setEditState=(edit)=>{
        this.setState({
            edit
        });
   }
 
   componentDidMount(){
       console.log("组件挂载完毕");
   }
 
   componentDidUpdate(){
       console.log("组件更新完毕");
   }
 
   render(){
      let {text,edit} = this.state;
      return (<div>
            {edit?
                <input
                    type="text"
                    value={text}
                    onChange={
                        (e) => {
                            this.setState({
                            text:e.target.value
                            })
                        }
                    }
                    onBlur={
                        ()=>{
                            this.setState({
                                edit:false
                            })
                        }
                    }
                />
                :
                <div >{text}  <a onClick={()=>{
                    this.setState({
                        edit: true
                    })
                }}>编辑</a>
                </div>
            }
      </div>);
   }
}
 
export default Effect;

组件更新完毕打印了好几次,因为我们更新了好几次,更新之后组件又会重新渲染。

3.1.5 example02-4

发现类组件使用生命周期,信息传递比较麻烦加入,建议融入函数式组件。

import React,{Component,useState} from "react";
 
function Txt(props){
    let {text,setEdit} = props;
 
    return (
        <div>{text}<a onClick={()=>{
            setEdit(true);
        }}>编辑</a></div>
    );
}
 
class Effect extends Component{
   state = {
        text: "这是今天的进度",
        edit: false  // 是否显示编辑状态
   }
 
   // 更新编辑方法
   setEditState=(edit)=>{
        this.setState({
            edit
        });
   }
 
 
   componentDidMount(){
       console.log("组件挂载完毕");
   }
 
   componentDidUpdate(){
       console.log("组件更新完毕");
   }
 
   render(){
      let {text,edit} = this.state;
      return (<div>
            {edit?
                <input
                    type="text"
                    value={text}
                    onChange={
                        (e) => {
                            this.setState({
                            text:e.target.value
                            })
                        }
                    }
                    onBlur={
                        ()=>{
                            this.setEditState(false);
                        }
                    }
                />
                :
                <Txt text={text} setEdit={this.setEditState}/>
            }
      </div>);
   }
}
 
export default Effect;

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.02-5
Branch: bhStudy-Hooks

commit description:as0.02-5-example02-5(react的生命周期useEffect使用——生命周期函数—组件挂载完毕、更新完毕,融入函数式组件)

tag:as0.02-5

3.1.6 example02-6

生命周期函数——组件卸载完毕(函数式组件)

import React,{Component,useState} from "react";
 
class Txt extends Component{
    componentWillUnmount(){
        console.log("组件即将卸载");
    }
    render(){
        let {text,setEdit} = this.props;
        return (
            <div>{text}<a onClick={()=>{
                setEdit(true);
            }}>编辑</a></div>
        );
    }
}
 
class Effect extends Component{
   state = {
        text: "这是今天的进度",
        edit: false  // 是否显示编辑状态
   }
 
   // 更新编辑方法
   setEditState=(edit)=>{
        this.setState({
            edit
        });
   }
 
 
   componentDidMount(){
       console.log("组件挂载完毕");
   }
 
   componentDidUpdate(){
       console.log("组件更新完毕");
   }
 
   render(){
      let {text,edit} = this.state;
      return (<div>
            {edit?
                <input
                    type="text"
                    value={text}
                    onChange={
                        (e) => {
                            this.setState({
                            text:e.target.value
                            })
                        }
                    }
                    onBlur={
                        ()=>{
                            this.setEditState(false);
                        }
                    }
                />
                :
                <Txt text={text} setEdit={this.setEditState}/>
            }
      </div>);
   }
}
 
export default Effect;

点击编辑后,Txt组件就会被卸载,紧接着渲染input组件。

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.02-6
Branch: bhStudy-Hooks

commit description:as0.02-6-example02-6(react的生命周期useEffect使用——生命周期函数—组件挂载完毕、更新完毕,组件卸载完毕(函数式组件))

tag:as0.02-6

3.1.7 example02-7

以上主要演示的是类组件的生命周期,现在完全用函数式组件演示生命周期。实际上hooks并没有生命周期,Effect副作用回调,实际就是以上三个生命周期,组件挂载完毕、更新完毕,组件即将卸载,用Effect呈现。

import React, { Component, useState, useEffect } from 'react';
 
function Txt(props){
    let {text,setEdit} = props;
    useEffect(()=>{
        console.log("组件状态有变化了");
    });
    return (
        <div>{text}<a onClick={()=>{
            setEdit(true);
        }}>编辑</a></div>
    );
}
function Effect(){
    const [text,setText] = useState("这是今天的课程");
    const [edit,setEdit] = useState(false);
    // useEffect接收回调函数
    useEffect(()=>{
        console.log("状态有改变");
    });
    return (<div>
        {edit?
            <input
                type="text"
                value = {text}
                onChange = {
                    (e)=>{
                        setText(e.target.value);
                    }
                }
                onBlur = {
                    ()=>{
                        setEdit(false)
                    }
                }
            />
            :
            <Txt text={text} setEdit={setEdit} />
        }
    </div>);
}
 
export default Effect;

首页渲染的时候两个组件都打印出来变化,点击编辑发现Effect打印了变化,而Txt组件未打印变化。

然后失去焦点,再重新渲染的时候,两个组件都发生了变化。

我们发现这个时候貌似监听组件卸载了,其实是可以监听的,只是写法变了。

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.02-7
Branch: bhStudy-Hooks

commit description:as0.02-7-example02-7(react的生命周期useEffect使用——用函数式组件演示生命周期,缺组件即将卸载的周期)

tag:as0.02-7

3.1.8 example02-8

演示组件挂载完毕、更新完毕,组件即将卸载,用Effect呈现。

import React, { Component, useState, useEffect } from 'react';
 
function Txt(props){
    let {text,setEdit} = props;
    useEffect(()=>{
        console.log("组件状态有变化了");
        return ()=>{
            console.log("txt组件卸载了");
        }
    });
    return (
        <div>{text}<a onClick={()=>{
            setEdit(true);
        }}>编辑</a></div>
    );
}
function Effect(){
    const [text,setText] = useState("这是今天的课程");
    const [edit,setEdit] = useState(false);
    // useEffect接收回调函数
    useEffect(()=>{
        console.log("状态有改变");
    });
    return (<div>
        {edit?
            <input
                type="text"
                value = {text}
                onChange = {
                    (e)=>{
                        setText(e.target.value);
                    }
                }
                onBlur = {
                    ()=>{
                        setEdit(false)
                    }
                }
            />
            :
            <Txt text={text} setEdit={setEdit} />
        }
    </div>);
}
 
export default Effect;

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.02-8
Branch: bhStudy-Hooks

commit description:as0.02-8-example02-8(react的生命周期useEffect使用——用函数式组件演示生命周期,完成)

tag:as0.02-8

useEffect意思是副作用,其实就相当于类组件的componentDidMount、componentDidUpdate 和 componentWillUnmount

3.2 example03

需要清除的副作用

我们不仅要检测组件状态变化,可能需要检测更具体一些,检测到具体哪个组件变化了?或者组件仅仅在卸载的时候执行等等,如何处理呢?

需求:页面文字很多,会有滚动条,实现input框始终保持在顶部不随滚动条变化。

3.2.1 example03-1

框子

import React, {useState, useEffect } from 'react';
 
function Txt(props){
    let {text,setEdit} = props;
    return (
        <div>{text}<a onClick={()=>{
            setEdit(true);
        }}>编辑</a></div>
    );
}

function Edit(props){
    const {text,setText,setEdit} = props;
    return (<input
        type="text"
        value = {text}
        onChange = {
            (e)=>{
                setText(e.target.value);
            }
        }
        onBlur = {
            ()=>{
                setEdit(false)
            }
        }
    />)
}

function Effect(){
    const [text,setText] = useState("这是今天的课程");
    const [edit,setEdit] = useState(false);
    return (<div>
        {edit?
            <Edit
                text = {text}
                setText = {setText}
                setEdit = {setEdit}
            />
            :
            <Txt text={text} setEdit={setEdit} />
        }
        {[...(".".repeat(100))].map((item,index)=>{
            return <div key={index}>页面内容填充</div>
        })}
    </div>);
}
 
 
export default Effect;
 

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.03-1
Branch: bhStudy-Hooks

commit description:as0.03-1-example03-1(react的生命周期useEffect使用——实现input框始终保持在顶部不随滚动条变化-框子实现)

tag:as0.03-1

3.2.2 example03-2

监听组件状态变化

function Edit(props){
    const {text,setText,setEdit} = props;
    useEffect(()=>{
        console.log("Edit组件更新了");
    });
    return (<input
        type="text"
        value = {text}
        onChange = {
            (e)=>{
                setText(e.target.value);
            }
        }
        onBlur = {
            ()=>{
                setEdit(false)
            }
        }
    />)
}

以上只是针对组件的挂载完毕和更新完毕才会触发。只要其中之一的状态发生变化,它就会触发。

但是有的时候,这个副作用只想监听某一个状态的变化,而其他状态变化并不会触发,这要如何处理呢?

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.03-2
Branch: bhStudy-Hooks

commit description:as0.03-2-example03-2(react的生命周期useEffect使用——实现input框始终保持在顶部不随滚动条变化-监听组件状态变化)

tag:as0.03-2

3.2.3 example03-3

只监听 edit发生改变

useEffect第二个参数是当第二个参数的状态发生改变,才会触发该函数

function Effect(){
    const [text,setText] = useState("这是今天的课程");
    const [edit,setEdit] = useState(false);
    // 只监听 edit 发生改变
    useEffect(()=>{
        console.log("组件更新了");
    },edit);
    return (<div>
        {edit?
            <Edit
                text = {text}
                setText = {setText}
                setEdit = {setEdit}
            />
            :
            <Txt text={text} setEdit={setEdit} />
        }
        {[...(".".repeat(100))].map((item,index)=>{
            return <div key={index}>页面内容填充</div>
        })}
    </div>);
}

但是报错了(先不管)。

useEffect的第二个参数实际是一个数组,数组里是它要监测的值,即可以监测多个值发生变化。

image-20200709121132918

    useEffect(()=>{
        console.log("组件更新了");
    },[edit,text]);

    useEffect(()=>{
        console.log("组件更新了");
    },[edit]);

编辑状态发生变化才会触发,而text改变的时候并没有执行了。

如果只希望组件挂载的时候执行,只要传入空数组就行了。

    useEffect(()=>{
        console.log("组件更新了");
    },[]);

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.03-3
Branch: bhStudy-Hooks

commit description:as0.03-3-example03-3(react的生命周期useEffect使用——实现input框始终保持在顶部不随滚动条变化-监听特定组件状态变化)

tag:as0.03-3

3.2.4 example03-4

注意不要忽略useEffect第二个参数的设置,否则会出问题。

import React, {useState, useEffect } from 'react';
 
function Txt(props){
    let {text,setEdit} = props;
    return (
        <div>{text}<a onClick={()=>{
            setEdit(true);
        }}>编辑</a></div>
    );
}
function Edit(props){
    const {text,setText,setEdit} = props;
    useEffect(()=>{
        window.addEventListener("scroll",()=>{
            console.log(document.querySelector("#txt"));
        });
        console.log(1);
    })
    return (<input
        type="text"
        value = {text}
        id = "txt"
        onChange = {
            (e)=>{
                setText(e.target.value);
            }
        }
        onBlur = {
            ()=>{
                setEdit(false)
            }
        }
    />)
}
function Effect(){
    const [text,setText] = useState("这是今天的课程");
    const [edit,setEdit] = useState(false);
    useEffect(()=>{
        console.log("Effect组件更新了");
    },[]);
    return (<div>
        {edit?
            <Edit
                text = {text}
                setText = {setText}
                setEdit = {setEdit}
            />
            :
            <Txt text={text} setEdit={setEdit} />
        }
        {[...(".".repeat(100))].map((item,index)=>{
            return <div key={index}>页面内容填充</div>
        })}
    </div>);
}
 
 
export default Effect;
 

我们发现滚动条滚动的时候,会触发很多次事件,那是因为,事件添加了很多次(每次触发useEffect,都会追加事件监听)。

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.03-4
Branch: bhStudy-Hooks

commit description:as0.03-4-example03-4(react的生命周期useEffect使用——实现input框始终保持在顶部不随滚动条变化-注意不要忽略useEffect第二个参数的设置,否则会出问题。)

tag:as0.03-4

3.2.5 example03-5

希望Edit组件第一次挂载的时候执行一次,后面就不要重复执行了。

我们添加事件监听,也就可以仅仅添加一次了。

import React, {useState, useEffect } from 'react';
 
function Txt(props){
    let {text,setEdit} = props;
    return (
        <div>{text}<a onClick={()=>{
            setEdit(true);
        }}>编辑</a></div>
    );
}
function Edit(props){
    const {text,setText,setEdit} = props;
    function toScroll(){
        let txt = document.querySelector("#txt");
        let y = window.scrollY;
        // 修改位移值相对于滚动条进行位移
        txt.style.transform = `translateY(${y}px)`;
        console.log(y);
    }
    useEffect(()=>{
        window.addEventListener("scroll",toScroll);
    },[])
    return (<input
        type="text"
        value = {text}
        id = "txt"
        onChange = {
            (e)=>{
                setText(e.target.value);
            }
        }
        onBlur = {
            ()=>{
                setEdit(false)
            }
        }
    />)
}
function Effect(){
    const [text,setText] = useState("这是今天的课程");
    const [edit,setEdit] = useState(false);
    useEffect(()=>{
        console.log("Effect组件更新了");
    },[]);
    return (<div>
        {edit?
            <Edit
                text = {text}
                setText = {setText}
                setEdit = {setEdit}
            />
            :
            <Txt text={text} setEdit={setEdit} />
        }
        {[...(".".repeat(100))].map((item,index)=>{
            return <div key={index}>页面内容填充</div>
        })}
    </div>);
}
 
 
export default Effect;
 

然而出现有点跳的情况,可以用绝对定位和固定定位来解决这个问题,这里不是重点也就不解决了。

不过仍然存在问题,我们发现组件卸载了之后,事件仍然在触发。如果观察F12Elementsinput标签实际已经被卸载了,因此这种情况出现,就很容易报错或发生奇异的问题。虽然元素已经从dom中卸载,但还存在内存中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SpoR9sp8-1595228445478)(https://cdn.jsdelivr.net/gh/6xiaoDi/blogpic/images/20200709_01/20200709131822.gif)]

image-20200709131637055

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.03-5
Branch: bhStudy-Hooks

commit description:as0.03-5-example03-5(react的生命周期useEffect使用——实现input框始终保持在顶部不随滚动条变化-添加事件监听,仅仅添加一次了,但出了问题。)

tag:as0.03-5

3.2.6 example03-6

需要清除的副作用

组件卸载之后,需要把事件也删除了。

import React, {useState, useEffect } from 'react';
 
function Txt(props){
    let {text,setEdit} = props;
    return (
        <div>{text}<a onClick={()=>{
            setEdit(true);
        }}>编辑</a></div>
    );
}
function Edit(props){
    const {text,setText,setEdit} = props;
    function toScroll(){
        let txt = document.querySelector("#txt");
        let y = window.scrollY;
        // 修改位移值相对于滚动条进行位移
        txt.style.transform = `translateY(${y}px)`;
        console.log(y);
    }
    useEffect(()=>{
        window.addEventListener("scroll",toScroll);
        return ()=>{
            console.log("Edit组件卸载了");
            window.removeEventListener("scroll",toScroll);
        }
    },[])
    return (<input
        type="text"
        value = {text}
        id = "txt"
        onChange = {
            (e)=>{
                setText(e.target.value);
            }
        }
        onBlur = {
            ()=>{
                setEdit(false)
            }
        }
    />)
}
function Effect(){
    const [text,setText] = useState("这是今天的课程");
    const [edit,setEdit] = useState(false);
    useEffect(()=>{
        console.log("Effect组件更新了");
    },[]);
    return (<div>
        {edit?
            <Edit
                text = {text}
                setText = {setText}
                setEdit = {setEdit}
            />
            :
            <Txt text={text} setEdit={setEdit} />
        }
        {[...(".".repeat(100))].map((item,index)=>{
            return <div key={index}>页面内容填充</div>
        })}
    </div>);
}
 
 
export default Effect;

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.03-6
Branch: bhStudy-Hooks

commit description:as0.03-6-example03-6(react的生命周期useEffect使用——实现input框始终保持在顶部不随滚动条变化-添加事件监听,仅仅添加一次了,组件卸载之后,需要把事件也删除了。)

tag:as0.03-6

3.2.7 小结

如果希望挂载阶段和更新阶段都执行useEffect,那就别加第二个参数。

第二个参数为空数组,默认只在挂载阶段执行。

如果仅仅希望更新阶段执行,同样也别加第二个参数,但需要再引进一个开关变量来控制。

希望卸载阶段完成,就返回一个函数来进行卸载工作。

4. useRef

4.1 example04

上面案例,实际上切换到input框的时候,焦点实际是自动聚焦上去的(我们这里没有)。这个如何实现呢?需要加上focus

获取dom节点,在类组件中用ref,到了hooksuseRef

另外它还有别的作用,之前组件更新的时候,可获取上次状态和当前次状态进行对比,但通过useEffect拿不到之前的状态,如果想获取上次的状态如何处理呢?

这就需要用到了useRef

4.1.1 example04-1

useRef最基本作用就是保存子组件、dom

实现input组件在挂载后,可以直接获取到焦点,省得还得点击一下,不点就没有焦点,自然就没有失焦。

import React, {useState, useEffect, useRef } from 'react';
 
function Txt(props){
    let {text,setEdit} = props;
    return (
        <div>{text}<a onClick={()=>{
            setEdit(true);
        }}>编辑</a></div>
    );
}
 
function Edit(props){
    const {text,setText,setEdit} = props;
    let t = useRef();
    console.log(t);
    function toScroll(){
        let txt = document.querySelector("#txt");
        let y = window.scrollY;
        // 修改位移值相对于滚动条进行位移
        txt.style.transform = `translateY(${y}px)`;
        console.log(y);
    }
    useEffect(()=>{
        window.addEventListener("scroll",toScroll);
        return ()=>{
            console.log("Edit组件卸载了");
            window.removeEventListener("scroll",toScroll);
        }
    },[])
    return (<input
        type="text"
        value = {text}
        id = "txt"
        onChange = {
            (e)=>{
                setText(e.target.value);
            }
        }
        onBlur = {
            ()=>{
                setEdit(false)
            }
        }
    />)
}
function Ref(){
    const [text,setText] = useState("这是今天的课程");
    const [edit,setEdit] = useState(false);
    useEffect(()=>{
        console.log("Effect组件更新了");
    },[]);
    return (<div>
        {edit?
            <Edit
                text = {text}
                setText = {setText}
                setEdit = {setEdit}
            />
            :
            <Txt text={text} setEdit={setEdit} />
        }
        {[...(".".repeat(100))].map((item,index)=>{
            return <div key={index}>页面内容填充</div>
        })}
    </div>);
}
 
 
export default Ref;
 

打印出看到useRef返回一个对象,对象下有一个属性叫current

image-20200709144841725

current可以设置一个初始值。

function Edit(props){
    const {text,setText,setEdit} = props;
    let t = useRef(1);
    console.log(t);
    ......
    }

image-20200709145121517

current一般设置为null,希望子组件初始为空对象,使用起来和ref没有太大的区别。

import React, {useState, useEffect, useRef } from 'react';
 
function Txt(props){
    let {text,setEdit} = props;
    return (
        <div>{text}<a onClick={()=>{
            setEdit(true);
        }}>编辑</a></div>
    );
}
 
function Edit(props){
    const {text,setText,setEdit} = props;
    let t = useRef(null);
    function toScroll(){
        let txt = document.querySelector("#txt");
        let y = window.scrollY;
        // 修改位移值相对于滚动条进行位移
        txt.style.transform = `translateY(${y}px)`;
        console.log(y);
    }
    useEffect(()=>{
        console.log(t);
        window.addEventListener("scroll",toScroll);
        return ()=>{
            console.log("Edit组件卸载了");
            window.removeEventListener("scroll",toScroll);
        }
    },[])
    return (<input
        type="text"
        value = {text}
        id = "txt"
        ref={t}
        onChange = {
            (e)=>{
                setText(e.target.value);
            }
        }
        onBlur = {
            ()=>{
                setEdit(false)
            }
        }
    />)
}
function Ref(){
    const [text,setText] = useState("这是今天的课程");
    const [edit,setEdit] = useState(false);
    useEffect(()=>{
        console.log("Effect组件更新了");
    },[]);
    return (<div>
        {edit?
            <Edit
                text = {text}
                setText = {setText}
                setEdit = {setEdit}
            />
            :
            <Txt text={text} setEdit={setEdit} />
        }
        {[...(".".repeat(100))].map((item,index)=>{
            return <div key={index}>页面内容填充</div>
        })}
    </div>);
}
 
 
export default Ref;
 

image-20200709145722510

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.04-1
Branch: bhStudy-Hooks

commit description:as0.04-1-example04-1(useRef——useRef最基本作用)

tag:as0.04-1

4.1.2 example04-2

通过上例发现其实就不需要用原生获取dom节点了,切换到input框的时候,焦点是自动聚焦上去的,并且选中文字。

import React, {useState, useEffect, useRef } from 'react';
 
function Txt(props){
    let {text,setEdit} = props;
    return (
        <div>{text}<a onClick={()=>{
            setEdit(true);
        }}>编辑</a></div>
    );
}
 
function Edit(props){
    const {text,setText,setEdit} = props;
    let t = useRef(null);
    function toScroll(){
        let y = window.scrollY;
        // 修改位移值相对于滚动条进行位移
        t.current.style.transform = `translateY(${y}px)`;
        console.log(y);
    }
    useEffect(()=>{
        window.addEventListener("scroll",toScroll);
        t.current.select();
        return ()=>{
            window.removeEventListener("scroll",toScroll);
        }
    },[])
    return (<input
        type="text"
        value = {text}
        id = "txt"
        ref={t}
        onChange = {
            (e)=>{
                setText(e.target.value);
            }
        }
        onBlur = {
            ()=>{
                setEdit(false)
            }
        }
    />)
}
function Ref(){
    const [text,setText] = useState("这是今天的课程");
    const [edit,setEdit] = useState(false);
    useEffect(()=>{
        console.log("Effect组件更新了");
    },[]);
    return (<div>
        {edit?
            <Edit
                text = {text}
                setText = {setText}
                setEdit = {setEdit}
            />
            :
            <Txt text={text} setEdit={setEdit} />
        }
        {[...(".".repeat(100))].map((item,index)=>{
            return <div key={index}>页面内容填充</div>
        })}
    </div>);
}
 
 
export default Ref;

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.04-2
Branch: bhStudy-Hooks

commit description:as0.04-2-example04-2(useRef——焦点是自动聚焦)

tag:as0.04-2

4.2 example05

useRef除此之外它还能保存之前的值或者状态。

4.2.1 example05-1

import React, {useState, useEffect, useRef } from 'react';
 
function Txt(props){
    let {text,setEdit} = props;
    return (
        <div>{text}<a onClick={()=>{
            setEdit(true);
        }}>编辑</a></div>
    );
}
function Edit(props){
    const {text,setText,setEdit} = props;
    let t = useRef(null);
 
    function toScroll(){
        let y = window.scrollY;
        t.current.style.transform = `translateY(${y}px)`;
        console.log(y);
    }
    useEffect(()=>{
        window.addEventListener("scroll",toScroll);
        t.current.select();
        return ()=>{
            window.removeEventListener("scroll",toScroll);
        }
    },[])
    return (<input
        type="text"
        value = {text}
        id = "txt"
        ref = {t}
        onChange = {
            (e)=>{
                setText(e.target.value);
            }
        }
        onBlur = {
            ()=>{
                setEdit(false)
            }
        }
    />)
}
 
function Ref(){
    const [nub,setNub] = useState(0);
    const prev = useRef(nub);
 
    return (<div>
        <p>当前值: {nub}</p>
        <p>上次值: {prev.current}</p>
        <button onClick={()=>{
            setNub(nub + 1);
        }}>递增</button>
    </div>);
}
 
 
export default Ref;
 

发现只增加了当前值,但是上次值不动。

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.05-1
Branch: bhStudy-Hooks

commit description:as0.05-1-example05-1(useRef——记录上次值但没有更新)

tag:as0.05-1

4.2.2 example05-2

useRef怎么记录上次值呢?

首先我们知道state的变化会导致组件的重新渲染,我们这里已经能看到新值重新渲染了,而ref并没有改变。

正常情况下组件更新是不会影响ref改变的,除非我们对ref做了绑定,此时我们并没把它与dom节点(子组件)绑定。这里回顾一下useEffect,副作用的钩子是在什么情形下执行的呢?

组件更新完或者组件挂载完才会执行,在这里组件挂载完后,状态更新完实际先更新dom,更新完dom后才会执行副作用钩子。我们先看效果:

function Ref(){
    const [nub,setNub] = useState(0);
    const prev = useRef(nub);
    useEffect(()=>{
        prev.current = nub;
    });
    return (<div>
        <p>当前值: {nub}</p>
        <p>上次值: {prev.current}</p>
        <button onClick={()=>{
            setNub(nub + 1);
        }}>递增</button>
    </div>);
}
 

我们再分析代码:首先设置一个state,并不执行useEffect。首先先把函数组件返回值挂载到dom中,挂载后才会更新副作用钩子,然后把nub最新值同步到ref中。然后等到再次修改的时候,修改完状态,会更新我们dom,这回的ref是没有改变的,是上一次的值。更新完毕后,才会执行副作用钩子,才会更新ref,记录最新的值。

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.05-2
Branch: bhStudy-Hooks

commit description:as0.05-2-example05-2(useRef——记录上次值并实时更新)

tag:as0.05-2

5. 综合实例(官网实例)——TodoLists

这样经常使用的钩子,我们已经说明了基本的功能。我们来做一个案例练习MVVM框架的入门练习:TodoLists

输入内容回车之后,可添加到下面的信息栏。除了这个添加功能之外还有可以选中任务,代表这个任务做完了。还有全选和全不选的功能。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7kwJmc8w-1595228445509)(https://cdn.jsdelivr.net/gh/6xiaoDi/blogpic/images/20200709_01/20200709155517.gif)]

以及删除功能,删除同时会同步全选和全不选的功能,除此之外还可以清除所有已完成的任务。以及单对单条的编辑功能。

我们仔细观察todolist的静态文件,再移植到react

image-20200709161409775

5.1 example06

实现TodoLists

5.1.1 example06-1

框子实现——对照其静态html文件复制即可。

将样式抽取出来index.css

将其划分为index.js,下面有header.js、mian.js、footer.js组件。

mian.js内容比较多,为了功能独立,我们再单另把li拿出来封装,偷懒就不再新建js文件了,就在该文件中新建一个子组件。

但注意组件中style属性是对象,而不是html中的字符串(可以先删掉);并且受控组件事件也需要添加;htmlfor属性在组件中为htmlForclass属性在组件中为className。(如果是WebStorm会自动帮我们替换,但是style还是不行,建议先删除再弄!)

code\project\study-hooks\react-app\src\todos\index.css

html,
body {
    margin: 0;
    padding: 0;
}
 
body {
    font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
    line-height: 1.4em;
    background: #eeeeee;
    color: #333333;
    width: 520px;
    margin: 0 auto;
    -webkit-font-smoothing: antialiased;
}
 
#todoapp {
    background: #fff;
    padding: 20px;
    margin-bottom: 40px;
    -ms-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
    -o-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
    box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
    border-radius: 0 0 5px 5px;
}
 
#todoapp h1 {
    font-size: 36px;
    font-weight: bold;
    text-align: center;
    padding: 0 0 10px 0;
}
 
#todoapp input[type="text"] {
    width: 466px;
    font-size: 24px;
    font-family: inherit;
    line-height: 1.4em;
    border: 0;
    outline: none;
    padding: 6px;
    border: 1px solid #999999;
    -ms-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
    -o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
    box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
}
 
#todoapp input::-webkit-input-placeholder {
    font-style: italic;
}
 
#main {
    display: none;
}
 
#todo-list {
    margin: 10px 0;
    padding: 0;
    list-style: none;
}
 
#todo-list li {
    padding: 18px 20px 18px 0;
    position: relative;
    font-size: 24px;
    border-bottom: 1px solid #cccccc;
}
 
#todo-list li:last-child {
    border-bottom: none;
}
 
#todo-list li.done label {
    color: #777777;
    text-decoration: line-through;
}
 
#todo-list .destroy {
    position: absolute;
    right: 5px;
    top: 20px;
    display: none;
    cursor: pointer;
    width: 20px;
    height: 20px;
    background: url(destroy.png) no-repeat;
}
 
#todo-list li:hover .destroy {
    display: block;
}
 
#todo-list .destroy:hover {
    background-position: 0 -20px;
}
 
#todo-list li.editing {
    border-bottom: none;
    margin-top: -1px;
    padding: 0;
}
 
#todo-list li.editing:last-child {
    margin-bottom: -1px;
}
 
#todo-list li.editing .edit {
    display: block;
    width: 444px;
    padding: 13px 15px 14px 20px;
    margin: 0;
}
 
#todo-list li.editing .view {
    display: none;
}
 
#todo-list li .view label {
    word-break: break-word;
}
 
#todo-list li .edit {
    display: none;
}
 
#todoapp footer {
    display: none;
    margin: 0 -20px -20px -20px;
    overflow: hidden;
    color: #555555;
    background: #f4fce8;
    border-top: 1px solid #ededed;
    padding: 0 20px;
    line-height: 37px;
    border-radius: 0 0 5px 5px;
}
 
#clear-completed {
    float: right;
    line-height: 20px;
    text-decoration: none;
    background: rgba(0, 0, 0, 0.1);
    color: #555555;
    font-size: 11px;
    margin-top: 8px;
    margin-bottom: 8px;
    padding: 0 10px 1px;
    cursor: pointer;
    border-radius: 12px;
    -ms-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
    -o-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
    box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
}
 
#clear-completed:hover {
    background: rgba(0, 0, 0, 0.15);
    -ms-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
    -o-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
    box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
}
 
#clear-completed:active {
    position: relative;
    top: 1px;
}
 
#todo-count span {
    font-weight: bold;
}
 
#instructions {
    margin: 10px auto;
    color: #777777;
    text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0;
    text-align: center;
}
 
#instructions a {
    color: #336699;
}
 
#credits {
    margin: 30px auto;
    color: #999;
    text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0;
    text-align: center;
}
 
#credits a {
    color: #888;
}

code\project\study-hooks\react-app\src\todos\index.js

import React, {useState, useEffect, useRef } from 'react';
import "./index.css"
import Header from "./header";
import Main from  "./main"
import Footer from "./footer";
 
function Todos(){
 
    return (<div id="todoapp">
        <Header/>
        <Main/>
        <Footer/>
    </div>);
}
 
 
export default Todos;
 

code\project\study-hooks\react-app\src\todos\header.js

import React, {useState} from 'react';
function Header(){
 
  return (<header>
            <h1>Todos</h1>
            <input id="new-todo" type="text" placeholder="What needs to be done?" value=""/>
         </header>);
}
 
 
export default Header;
 

code\project\study-hooks\react-app\src\todos\main.js

import React, {useState, useEffect, useRef } from 'react';
function Li(){
    return (
        <li className="">
            <div className="view" >
                <input className="toggle" type="checkbox"/>
                <label>213213</label>
                <a className="destroy">
 
                </a>
            </div>
            <input className="edit" type="text" value="213213"/>
        </li>
    )
}
 
function Mian(){
  return (
      <section id="main" >
          <input id="toggle-all" type="checkbox" checked=""/>
          <label htmlFor="toggle-all">Mark all as complete</label>
          <ul id="todo-list">
              <Li/>
          </ul>
      </section>
    );
}
 
 
export default Mian;
 

code\project\study-hooks\react-app\src\todos\footer.js

import React from 'react';
function Footer(){
      return (
          <footer >
              <a id="clear-completed" >Clear 0 completed item</a>
              <div id="todo-count">10 items left</div>
          </footer>
      );
}
 
 
export default Footer;
 

image-20200709185849446

image-20200709185902641

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.06-1
Branch: bhStudy-Hooks

commit description:as0.06-1-example06-1(实现TodoLists——静态框子)

tag:as0.06-1

5.1.2 example06-2

index中首先建立所有存储TODO的状态,用数组来保存。

写个方法实现添加功能,并将添加方法传给子组件Header,同样在Header组件中首先建立所有存储TODO的状态,用字符串来保存,并将其内的input组件做成受控组件,通过onchange事件来更新todo状态值。

Header组件中添加回车事件,如果内容为空则提示并让焦点回到input框上,如果不为空则调用父组件传进的回调函数,并将todo作为参数传过去。

code\project\study-hooks\react-app\src\todos\index.js

import React, {useState, useEffect, useRef } from 'react';
import "./index.css"
import Header from "./header";
import Main from  "./main"
import Footer from "./footer";
 
function Todos(){
    // 用数组来保存所有TODO状态
    const [todos,setTodos] = useState([]);
 
    function addTodo(val){
        console.log(val);
    }
 
 
    return (<div id="todoapp">
        <Header addTodo = {addTodo}/>
        <Main/>
        <Footer/>
    </div>);
}
 
 
export default Todos;
 

code\project\study-hooks\react-app\src\todos\header.js

import React, {useState} from 'react';
function Header(props){
    const [todo,setTodo] = useState("");
    let {addTodo} = props;
    return (<header>
            <h1>Todos</h1>
            <input
                id="new-todo"
                type="text"
                placeholder="What needs to be done?"
                value={todo}
                onChange={(e) => {
                        setTodo(e.target.value);
                    }
                }
 
                onKeyDown={(e) => {  // 回车事件
                    // 判断如果是回车
                    if (e.keyCode == 13){
                        if (!todo.trim()){
                            alert("输入内容不能为空!");
                            e.target.focus();
                        }else{
                            addTodo(todo);
                            // 清空
                            setTodo('');
                        }
                    }
                }}
            />
         </header>);
}
 
 
export default Header;
 

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.06-2
Branch: bhStudy-Hooks

commit description:as0.06-2-example06-2(实现TodoLists——实现在Hear组件中将todo传递给父组件的回调函数)

tag:as0.06-2

5.1.3 example06-3

index中添加todos,注意本身在这里todos是一个对象类型,setState在这里只是做了浅对比,如果只是往里push的话,并不能引起state的修改,因此我们先加入原先的内容,并对其做解构,再添加新内容的新对象。这样才能触发组件更新,否则组件是不会更新的。

code\project\study-hooks\react-app\src\todos\index.js

function Todos(){
    // 用数组来保存所有TODO状态
    const [todos,setTodos] = useState([]);
 
    function addTodo(val){
        console.log();
        setTodos([...todos,{
            id: Date.now(), // 实际工作中后端提供
            val,
            completed: false // 选中状态
        }]);
    }
 
    console.log(todos);
    return (<div id="todoapp">
        <Header addTodo = {addTodo}/>
        <Main/>
        <Footer/>
    </div>);
}

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.06-3
Branch: bhStudy-Hooks

commit description:as0.06-3-example06-3(实现TodoLists——实现添加todos功能)

tag:as0.06-3

5.1.4 example06-4

实现拿到数据后,来完成后续的主体内容,显示并渲染。

code\project\study-hooks\react-app\src\todos\index.js

import React, {useState, useEffect, useRef } from 'react';
import "./index.css"
import Header from "./header";
import Main from  "./main"
import Footer from "./footer";
 
function Todos(){
    // 用数组来保存所有TODO状态
    const [todos,setTodos] = useState([]);
 
    function addTodo(val){
        console.log();
        setTodos([...todos,{
            id: Date.now(), // 实际工作中后端提供
            val,
            completed: false // 选中状态
        }]);
    }
 
    console.log(todos);
    return (<div id="todoapp">
        <Header addTodo = {addTodo}/>
        <Main todos={todos}/>
        <Footer/>
    </div>);
}
 
 
export default Todos;
 

code\project\study-hooks\react-app\src\todos\main.js

import React, {useState, useEffect, useRef } from 'react';
function Li(){
    return (
        <li className="">
            <div className="view" >
                <input className="toggle" type="checkbox"/>
                <label>213213</label>
                <a className="destroy">
 
                </a>
            </div>
            <input className="edit" type="text" value="213213"/>
        </li>
    )
}
 
function Mian(){
    let {todos} = this.props;
    return (
    <section
        id="main"
        style={{
            display: todos.length > 0 ? 'block' : 'none'
        }}
    >
        <input id="toggle-all" type="checkbox" checked=""/>
        <label htmlFor="toggle-all">Mark all as complete</label>
        <ul id="todo-list">
            <Li/>
        </ul>
    </section>
    );
}
 
 
export default Mian;
 

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.06-4
Branch: bhStudy-Hooks

commit description:as0.06-4-example06-4(实现TodoLists——实现部分todos渲染功能)

tag:as0.06-4

5.1.5 example06-5

接下来把数据关联起来。

code\project\study-hooks\react-app\src\todos\main.js

import React, {useState, useEffect, useRef } from 'react';
 
function Li(props){
    let {inner} = props;
    return (
        <li className={inner.completed ? "done" : ""}>
            <div className="view" >
                <input
                    className="toggle"
                    type="checkbox"
                    checked={inner.completed}
                />
                <label>{inner.val}</label>
                <a className="destroy">
 
                </a>
            </div>
            <input
                className="edit"
                type="text"
                value={inner.val}
            />
        </li>
    )
}
 
function Mian(props){
    let {todos} = props;
    return (
    <section
        id="main"
        style={{
            display: todos.length > 0 ? 'block' : 'none'
        }}
    >
        <input id="toggle-all" type="checkbox" checked=""/>
        <label htmlFor="toggle-all">Mark all as complete</label>
        <ul id="todo-list">
            {
                todos.map(item=> {
                    return <Li
                        key = {item.id}
                        inner = {item}
                    />
                })
            }
        </ul>
    </section>
    );
}
 
 
export default Mian;
 

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.06-5
Branch: bhStudy-Hooks

commit description:as0.06-5-example06-5(实现TodoLists——实现部分todos数据关联起来)

tag:as0.06-5

5.1.6 example06-6

checkbox变为受控组件,实现改变勾选后,出现删除线。。

code\project\study-hooks\react-app\src\todos\index.js

import React, {useState, useEffect, useRef } from 'react';
import "./index.css"
import Header from "./header";
import Main from  "./main"
import Footer from "./footer";
 
function Todos(){
    // 用数组来保存所有TODO状态
    const [todos,setTodos] = useState([]);
 
    function addTodo(val){
        console.log();
        setTodos([...todos,{
            id: Date.now(), // 实际工作中后端提供
            val,
            completed: false // 选中状态
        }]);
    }
 
    // 修养修改的对应项id
    function changeCompleted(id,completed){
        todos.forEach(item=>{
            if(id == item.id){
                item.completed = completed;
            }
        });
        // 不能直接传todos,因为对比的时候是浅层对比,还是认为同一个对象,是不会进行更新的,我们应该解构出来返回一个新数组
        setTodos([...todos]);
    }
 
    console.log(todos);
    return (<div id="todoapp">
        <Header addTodo = {addTodo}/>
        <Main
            todos={todos}
            changeCompleted={changeCompleted}
        />
        <Footer/>
    </div>);
}
 
 
export default Todos;
 

code\project\study-hooks\react-app\src\todos\main.js

import React, {useState, useEffect, useRef } from 'react';
 
function Li(props){
    let {inner,changeCompleted} = props;
    return (
        <li className={inner.completed ? "done" : ""}>
            <div className="view" >
                <input
                    className="toggle"
                    type="checkbox"
                    checked={inner.completed}
                    onChange={(e)=>{
                        changeCompleted(inner.id,e.target.checked);
                    }}
                />
                <label>{inner.val}</label>
                <a className="destroy">
 
                </a>
            </div>
            <input
                className="edit"
                type="text"
                value={inner.val}
            />
        </li>
    )
}
 
function Mian(props){
    let {todos,changeCompleted} = props;
    return (
    <section
        id="main"
        style={{
            display: todos.length > 0 ? 'block' : 'none'
        }}
    >
        <input id="toggle-all" type="checkbox" checked=""/>
        <label htmlFor="toggle-all">Mark all as complete</label>
        <ul id="todo-list">
            {
                todos.map(item=> {
                    return <Li
                        key = {item.id}
                        inner = {item}
                        changeCompleted = {changeCompleted}
                    />
                })
            }
        </ul>
    </section>
    );
}
 
 
export default Mian;
 

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.06-6
Branch: bhStudy-Hooks

commit description:as0.06-6-example06-6(实现TodoLists——实现部分todos数据对应项的勾选功能)

tag:as0.06-6

5.1.7 example06-7

实现单个删除功能

code\project\study-hooks\react-app\src\todos\index.js

import React, {useState, useEffect, useRef } from 'react';
import "./index.css"
import Header from "./header";
import Main from  "./main"
import Footer from "./footer";
 
function Todos(){
    // 用数组来保存所有TODO状态
    const [todos,setTodos] = useState([]);
 
    function addTodo(val){
        console.log();
        setTodos([...todos,{
            id: Date.now(), // 实际工作中后端提供
            val,
            completed: false // 选中状态
        }]);
    }
 
    // 修养修改的对应项id
    function changeCompleted(id,completed){
        todos.forEach(item=>{
            if(id == item.id){
                item.completed = completed;
            }
        });
        // 不能直接传todos,因为对比的时候是浅层对比,还是认为同一个对象,是不会进行更新的,我们应该解构出来返回一个新数组
        setTodos([...todos]);
    }
 
    function remove(id){
        setTodos(todos.filter(item=>item.id !== id));
    }
 
    console.log(todos);
    return (<div id="todoapp">
        <Header addTodo = {addTodo}/>
        <Main
            todos={todos}
            changeCompleted={changeCompleted}
            remove={remove}
        />
        <Footer/>
    </div>);
}
 
 
export default Todos;
 

code\project\study-hooks\react-app\src\todos\main.js

import React, {useState, useEffect, useRef } from 'react';
 
function Li(props){
    let {inner,changeCompleted,remove} = props;
    let {id} = inner;
    return (
        <li className={inner.completed ? "done" : ""}>
            <div className="view" >
                <input
                    className="toggle"
                    type="checkbox"
                    checked={inner.completed}
                    onChange={(e)=>{
                        changeCompleted(id,e.target.checked);
                    }}
                />
                <label>{inner.val}</label>
                <a className="destroy"
                   onClick={()=>{
                       remove(id);
                   }}
                >
                </a>
            </div>
            <input
                className="edit"
                type="text"
                value={inner.val}
            />
        </li>
    )
}
 
function Mian(props){
    let {todos} = props;
    return (
    <section
        id="main"
        style={{
            display: todos.length > 0 ? 'block' : 'none'
        }}
    >
        <input id="toggle-all" type="checkbox" checked=""/>
        <label htmlFor="toggle-all">Mark all as complete</label>
        <ul id="todo-list">
            {
                todos.map(item=> {
                    return <Li
                        key = {item.id}
                        inner = {item}
                        {...props}
                    />
                })
            }
        </ul>
    </section>
    );
}
 
 
export default Mian;
 

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.06-7
Branch: bhStudy-Hooks

commit description:as0.06-7-example06-7(实现TodoLists——实现单个删除功能)

tag:as0.06-7

5.1.8 example06-8

实现双击选项,出现编辑框,可以修改选项内容。(需要借助生命周期和ref)

5.1.8.1 example06-8-1

实现切换编辑框

code\project\study-hooks\react-app\src\todos\main.js

import React, {useState, useEffect, useRef } from 'react';
const [edit,setEdit] = useState(false);
function Li(props){
    let {inner,changeCompleted,remove} = props;
    let {id} = inner;
    return (
        <li className={inner.completed ? "done" : ""}>
            <div
                className="view"
                style = {{
                    display: edit?"none":"block"
                }}
            >
                <input
                    className="toggle"
                    type="checkbox"
                    checked={inner.completed}
                    onChange={(e)=>{
                        changeCompleted(id,e.target.checked);
                    }}
                />
                <label
                    onDoubleClick = {()=>{
                        setEdit(true);
                    }}
                >{inner.val}</label>
                <a className="destroy"
                   onClick={()=>{
                       remove(id);
                   }}
                >
                </a>
            </div>
            <input
                className="edit"
                type="text"
                value={inner.val}
                style={{
                    display:edit?"block":"none"
                }}
            />
        </li>
    )
}
 
function Mian(props){
    let {todos} = props;
    return (
    <section
        id="main"
        style={{
            display: todos.length > 0 ? 'block' : 'none'
        }}
    >
        <input id="toggle-all" type="checkbox" checked=""/>
        <label htmlFor="toggle-all">Mark all as complete</label>
        <ul id="todo-list">
            {
                todos.map(item=> {
                    return <Li
                        key = {item.id}
                        inner = {item}
                        {...props}
                    />
                })
            }
        </ul>
    </section>
    );
}
 
 
export default Mian;
 

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.06-8-1
Branch: bhStudy-Hooks

commit description:as0.06-8-1-example06-8-1(实现TodoLists——实现双击选项——实现切换编辑框)

tag:as0.06-8-1

5.1.8.2 example06-8-2

失去焦点后,状态可切换回来。

code\project\study-hooks\react-app\src\todos\main.js

import React, {useState, useEffect, useRef } from 'react';
function Li(props){
    let {inner,changeCompleted,remove} = props;
    let {id} = inner;
    const [edit,setEdit] = useState(false);
    return (
        <li className={inner.completed ? "done" : ""}>
            <div
                className="view"
                style = {{
                    display: edit?"none":"block"
                }}
            >
                <input
                    className="toggle"
                    type="checkbox"
                    checked={inner.completed}
                    onChange={(e)=>{
                        changeCompleted(id,e.target.checked);
                    }}
                />
                <label
                    onDoubleClick = {()=>{
                        setEdit(true);
                    }}
                >{inner.val}</label>
                <a className="destroy"
                   onClick={()=>{
                       remove(id);
                   }}
                >
                </a>
            </div>
            <input
                className="edit"
                type="text"
                value={inner.val}
                style={{
                    display:edit?"block":"none"
                }}
                onBlur={()=>{
                    setEdit(false);
                }}
            />
        </li>
    )
}
 
function Mian(props){
    let {todos} = props;
    return (
    <section
        id="main"
        style={{
            display: todos.length > 0 ? 'block' : 'none'
        }}
    >
        <input id="toggle-all" type="checkbox" checked=""/>
        <label htmlFor="toggle-all">Mark all as complete</label>
        <ul id="todo-list">
            {
                todos.map(item=> {
                    return <Li
                        key = {item.id}
                        inner = {item}
                        {...props}
                    />
                })
            }
        </ul>
    </section>
    );
}
 
 
export default Mian;
 

问题:切换的时候没让其立即得到的焦点就导致操作其他地方就不存在失焦这个问题,它就不会切换回去了,这就出现了好几个input的编辑状态的编辑框了。

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.06-8-2
Branch: bhStudy-Hooks

commit description:as0.06-8-2-example06-8-2(实现TodoLists——实现双击选项——实现切换编辑框,出现bug)

tag:as0.06-8-2

5.1.8.3 example06-8-3

通过副作用钩子解决切换的时候没让其立即得到的焦点就导致操作其他地方就不存在失焦这个问题。

通过副作用钩子使编辑框一出现,就立即选中其内容,同时焦点必然也在上面了。

code\project\study-hooks\react-app\src\todos\main.js

import React, {useState, useEffect, useRef } from 'react';
function Li(props){
    let {inner,changeCompleted,remove} = props;
    let {id} = inner;
    const [edit,setEdit] = useState(false);
    const elEdit = useRef(null);
    useEffect(()=>{
        if(edit){
            elEdit.current.select();
        }
    },[edit]);
    return (
        <li className={inner.completed ? "done" : ""}>
            <div
                className="view"
                style = {{
                    display: edit?"none":"block"
                }}
            >
                <input
                    className="toggle"
                    type="checkbox"
                    checked={inner.completed}
                    onChange={(e)=>{
                        changeCompleted(id,e.target.checked);
                    }}
                />
                <label
                    onDoubleClick = {()=>{
                        setEdit(true);
                    }}
                >{inner.val}</label>
                <a className="destroy"
                   onClick={()=>{
                       remove(id);
                   }}
                >
                </a>
            </div>
            <input
                className="edit"
                type="text"
                value={inner.val}
                ref={elEdit}
                style={{
                    display:edit?"block":"none"
                }}
                onBlur={()=>{
                    setEdit(false);
                }}
            />
        </li>
    )
}
 
function Mian(props){
    let {todos} = props;
    return (
    <section
        id="main"
        style={{
            display: todos.length > 0 ? 'block' : 'none'
        }}
    >
        <input id="toggle-all" type="checkbox" checked=""/>
        <label htmlFor="toggle-all">Mark all as complete</label>
        <ul id="todo-list">
            {
                todos.map(item=> {
                    return <Li
                        key = {item.id}
                        inner = {item}
                        {...props}
                    />
                })
            }
        </ul>
    </section>
    );
}
 
 
export default Mian;
 

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.06-8-3
Branch: bhStudy-Hooks

commit description:as0.06-8-3-example06-8-3(实现TodoLists——实现双击选项——实现切换编辑框,解决切换出现的bug)

tag:as0.06-8-3

5.1.9 example06-9

完善切换编辑框的编辑内容

code\project\study-hooks\react-app\src\todos\index.js

import React, {useState, useEffect, useRef } from 'react';
import "./index.css"
import Header from "./header";
import Main from  "./main"
import Footer from "./footer";
 
function Todos(){
    // 用数组来保存所有TODO状态
    const [todos,setTodos] = useState([]);
 
    function addTodo(val){
        console.log();
        setTodos([...todos,{
            id: Date.now(), // 实际工作中后端提供
            val,
            completed: false // 选中状态
        }]);
    }
 
    // 修养修改的对应项id
    function changeCompleted(id,completed){
        todos.forEach(item=>{
            if(id == item.id){
                item.completed = completed;
            }
        });
        // 不能直接传todos,因为对比的时候是浅层对比,还是认为同一个对象,是不会进行更新的,我们应该解构出来返回一个新数组
        setTodos([...todos]);
    }
 
    function remove(id){
        setTodos(todos.filter(item=>item.id !== id));
    }
 
    function editVal(id,val){
        todos.forEach(item=>{
            if(id == item.id){
                item.val = val;
            }
        });
        setTodos([...todos]);
    }
 
    return (<div id="todoapp">
        <Header addTodo = {addTodo}/>
        <Main
            todos={todos}
            changeCompleted={changeCompleted}
            remove={remove}
            editVal={editVal}
        />
        <Footer/>
    </div>);
}
 
 
export default Todos;
 

code\project\study-hooks\react-app\src\todos\main.js

import React, {useState, useEffect, useRef } from 'react';
function Li(props){
    let {inner,changeCompleted,remove,editVal} = props;
    let {id} = inner;
    const [edit,setEdit] = useState(false);
    const elEdit = useRef(null);
    useEffect(()=>{
        if(edit){
            elEdit.current.select();
        }
    },[edit]);
    return (
        <li className={inner.completed ? "done" : ""}>
            <div
                className="view"
                style = {{
                    display: edit?"none":"block"
                }}
            >
                <input
                    className="toggle"
                    type="checkbox"
                    checked={inner.completed}
                    onChange={(e)=>{
                        changeCompleted(id,e.target.checked);
                    }}
                />
                <label
                    onDoubleClick = {()=>{
                        setEdit(true);
                    }}
                >{inner.val}</label>
                <a className="destroy"
                   onClick={()=>{
                       remove(id);
                   }}
                >
                </a>
            </div>
            <input
                className="edit"
                type="text"
                value={inner.val}
                ref={elEdit}
                style={{
                    display:edit?"block":"none"
                }}
                onBlur={()=>{
                    setEdit(false);
                }}
                onChange={(e)=>{
                    editVal(id, e.target.value);
                }}
            />
        </li>
    )
}
 
function Mian(props){
    let {todos} = props;
    return (
    <section
        id="main"
        style={{
            display: todos.length > 0 ? 'block' : 'none'
        }}
    >
        <input id="toggle-all" type="checkbox" checked=""/>
        <label htmlFor="toggle-all">Mark all as complete</label>
        <ul id="todo-list">
            {
                todos.map(item=> {
                    return <Li
                        key = {item.id}
                        inner = {item}
                        {...props}
                    />
                })
            }
        </ul>
    </section>
    );
}
 
 
export default Mian;
 

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.06-9
Branch: bhStudy-Hooks

commit description:as0.06-9-example06-9(实现TodoLists——完善切换编辑框的编辑内容——但存在bug)

tag:as0.06-9

5.1.10 example06-10

以上问题:当编辑框内容为空,该选项还挂载页面上。

编辑为编辑框失去焦点后,在生命周期中判断如果内容为空,则不能失去焦点,必须有内容才行,即内容不得为空。

code\project\study-hooks\react-app\src\todos\main.js

import React, {useState, useEffect, useRef } from 'react';
function Li(props){
    let {inner,changeCompleted,remove,editVal} = props;
    let {id} = inner;
    const [edit,setEdit] = useState(false);
    const elEdit = useRef(null);
    useEffect(()=>{
        if(edit){
            elEdit.current.select();
        } else {
            // 如果为空:就不能干别的事情,焦点一直在编辑框
            if(!inner.val.trim()){
                setEdit(true);
            }
        }
    },[edit]);
    return (
        <li className={inner.completed ? "done" : ""}>
            <div
                className="view"
                style = {{
                    display: edit?"none":"block"
                }}
            >
                <input
                    className="toggle"
                    type="checkbox"
                    checked={inner.completed}
                    onChange={(e)=>{
                        changeCompleted(id,e.target.checked);
                    }}
                />
                <label
                    onDoubleClick = {()=>{
                        setEdit(true);
                    }}
                >{inner.val}</label>
                <a className="destroy"
                   onClick={()=>{
                       remove(id);
                   }}
                >
                </a>
            </div>
            <input
                className="edit"
                type="text"
                value={inner.val}
                ref={elEdit}
                style={{
                    display:edit?"block":"none"
                }}
                onBlur={()=>{
                    setEdit(false);
                }}
                onChange={(e)=>{
                    editVal(id, e.target.value);
                }}
            />
        </li>
    )
}
 
function Mian(props){
    let {todos} = props;
    return (
    <section
        id="main"
        style={{
            display: todos.length > 0 ? 'block' : 'none'
        }}
    >
        <input id="toggle-all" type="checkbox" checked=""/>
        <label htmlFor="toggle-all">Mark all as complete</label>
        <ul id="todo-list">
            {
                todos.map(item=> {
                    return <Li
                        key = {item.id}
                        inner = {item}
                        {...props}
                    />
                })
            }
        </ul>
    </section>
    );
}
 
 
export default Mian;
 

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.06-10
Branch: bhStudy-Hooks

commit description:as0.06-10-example06-10(实现TodoLists——完善切换编辑框的编辑内容——解决无内容失去焦点的bug)

tag:as0.06-10

5.1.11 example06-11

添加footer框子

code\project\study-hooks\react-app\src\todos\index.js

import React, {useState, useEffect, useRef } from 'react';
import "./index.css"
import Header from "./header";
import Main from  "./main"
import Footer from "./footer";
 
function Todos(){
    // 用数组来保存所有TODO状态
    const [todos,setTodos] = useState([]);
 
    function addTodo(val){
        console.log();
        setTodos([...todos,{
            id: Date.now(), // 实际工作中后端提供
            val,
            completed: false // 选中状态
        }]);
    }
 
    // 修养修改的对应项id
    function changeCompleted(id,completed){
        todos.forEach(item=>{
            if(id === item.id){
                item.completed = completed;
            }
        });
        // 不能直接传todos,因为对比的时候是浅层对比,还是认为同一个对象,是不会进行更新的,我们应该解构出来返回一个新数组
        setTodos([...todos]);
    }
 
    function remove(id){
        setTodos(todos.filter(item=>item.id !== id));
    }
 
    function editVal(id,val){
        todos.forEach(item=>{
            if(id === item.id){
                item.val = val;
            }
        });
        setTodos([...todos]);
    }
 
    return (<div id="todoapp">
        <Header addTodo = {addTodo}/>
        <Main
            todos={todos}
            changeCompleted={changeCompleted}
            remove={remove}
            editVal={editVal}
        />
        <Footer
            todos={todos}
        />
    </div>);
}
 
 
export default Todos;
 

code\project\study-hooks\react-app\src\todos\footer.js

import React from 'react';
function Footer(props){
    let {todos} = props;
      return (
          <footer
              style={{
                  display:todos.length > 0 ? "block":"none"
              }}
          >
              <a id="clear-completed" >Clear 0 completed item</a>
              <div id="todo-count">10 items left</div>
          </footer>
      );
}
 
 
export default Footer;
 

image-20200710111609967

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.06-11
Branch: bhStudy-Hooks

commit description:as0.06-11-example06-11(实现TodoLists——添加footer框子)

tag:as0.06-11

5.1.12 example06-12

添加footer功能,其左侧显示当前未完成任务,右侧显示已完成的任务。如果没有已完成的任务,右侧就不显示。

code\project\study-hooks\react-app\src\todos\footer.js

import React from 'react';
function Footer(props){
    let {todos} = props;
    let unCompleted = todos.filter(item=>!item.completed);
    let completed = todos.filter(item=>item.completed);
      return (
          <footer
              style={{
                  display:todos.length > 0 ? "block":"none"
              }}
          >
              <a id="clear-completed"
                 style={{
                     display:completed.length > 0?"block":"none"
                 }}
              >Clear {completed.length} completed item</a>
              <div id="todo-count">{unCompleted.length} items left</div>
          </footer>
      );
}
 
 
export default Footer;
 

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.06-12
Branch: bhStudy-Hooks

commit description:as0.06-12-example06-12(实现TodoLists——完成footer功能)

tag:as0.06-12

5.1.13 example06-13

完成clear功能:清除已完成的任务

code\project\study-hooks\react-app\src\todos\index.js

import React, {useState, useEffect, useRef } from 'react';
import "./index.css"
import Header from "./header";
import Main from  "./main"
import Footer from "./footer";
 
function Todos(){
    // 用数组来保存所有TODO状态
    const [todos,setTodos] = useState([]);
 
    function addTodo(val){
        console.log();
        setTodos([...todos,{
            id: Date.now(), // 实际工作中后端提供
            val,
            completed: false // 选中状态
        }]);
    }
 
    // 修养修改的对应项id
    function changeCompleted(id,completed){
        todos.forEach(item=>{
            if(id === item.id){
                item.completed = completed;
            }
        });
        // 不能直接传todos,因为对比的时候是浅层对比,还是认为同一个对象,是不会进行更新的,我们应该解构出来返回一个新数组
        setTodos([...todos]);
    }
 
    function remove(id){
        setTodos(todos.filter(item=>item.id !== id));
    }
 
    function removeCompleted(){
        setTodos(todos.filter(item=>!item.completed));
    }
 
    function editVal(id,val){
        todos.forEach(item=>{
            if(id === item.id){
                item.val = val;
            }
        });
        setTodos([...todos]);
    }
 
    return (<div id="todoapp">
        <Header addTodo = {addTodo}/>
        <Main
            todos={todos}
            changeCompleted={changeCompleted}
            remove={remove}
            editVal={editVal}
        />
        <Footer
            todos={todos}
            removeCompleted = {removeCompleted}
        />
    </div>);
}
 
 
export default Todos;
 

code\project\study-hooks\react-app\src\todos\footer.js

import React from 'react';
function Footer(props){
    let {todos,removeCompleted} = props;
    let unCompleted = todos.filter(item=>!item.completed);
    let completed = todos.filter(item=>item.completed);
      return (
          <footer
              style={{
                  display:todos.length > 0 ? "block":"none"
              }}
          >
              <a id="clear-completed"
                 style={{
                     display:completed.length > 0?"block":"none"
                 }}
                 onClick = {()=>{
                     removeCompleted();
                 }}
              >Clear {completed.length} completed item</a>
              <div id="todo-count">{unCompleted.length} items left</div>
          </footer>
      );
}
 
 
export default Footer;
 

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.06-13
Branch: bhStudy-Hooks

commit description:as0.06-13-example06-13(实现TodoLists——完成footer功能—完成clear功能)

tag:as0.06-13

5.1.14 example06-14

实现全选和全不选功能

code\project\study-hooks\react-app\src\todos\main.js

import React, {useState, useEffect, useRef } from 'react';
function Li(props){
    let {inner,changeCompleted,remove,editVal} = props;
    let {id} = inner;
    const [edit,setEdit] = useState(false);
    const elEdit = useRef(null);
    useEffect(()=>{
        if(edit){
            elEdit.current.select();
        } else {
            // 如果为空:就不能干别的事情,焦点一直在编辑框
            if(!inner.val.trim()){
                setEdit(true);
            }
        }
    },[edit]);
    return (
        <li className={inner.completed ? "done" : ""}>
            <div
                className="view"
                style = {{
                    display: edit?"none":"block"
                }}
            >
                <input
                    className="toggle"
                    type="checkbox"
                    checked={inner.completed}
                    onChange={(e)=>{
                        changeCompleted(id,e.target.checked);
                    }}
                />
                <label
                    onDoubleClick = {()=>{
                        setEdit(true);
                    }}
                >{inner.val}</label>
                <a className="destroy"
                   onClick={()=>{
                       remove(id);
                   }}
                >
                </a>
            </div>
            <input
                className="edit"
                type="text"
                value={inner.val}
                ref={elEdit}
                style={{
                    display:edit?"block":"none"
                }}
                onBlur={()=>{
                    setEdit(false);
                }}
                onChange={(e)=>{
                    editVal(id, e.target.value);
                }}
            />
        </li>
    )
}
 
function Mian(props){
    let {todos,changeAllCompleted} = props;
    let completed = todos.filter(item=>item.completed);
    return (
    <section
        id="main"
        style={{
            display: todos.length > 0 ? 'block' : 'none'
        }}
    >
        <input
            id="toggle-all"
            type="checkbox"
            checked={completed.length === todos.length}
            onChange={(e)=>{
                changeAllCompleted(e.target.checked);
            }}
        />
        <label htmlFor="toggle-all">Mark all as complete</label>
        <ul id="todo-list">
            {
                todos.map(item=> {
                    return <Li
                        key = {item.id}
                        inner = {item}
                        {...props}
                    />
                })
            }
        </ul>
    </section>
    );
}
 
 
export default Mian;
 

code\project\study-hooks\react-app\src\todos\index.js

import React, {useState, useEffect, useRef } from 'react';
import "./index.css"
import Header from "./header";
import Main from  "./main"
import Footer from "./footer";
 
function Todos(){
    // 用数组来保存所有TODO状态
    const [todos,setTodos] = useState([]);
 
    function addTodo(val){
        console.log();
        setTodos([...todos,{
            id: Date.now(), // 实际工作中后端提供
            val,
            completed: false // 选中状态
        }]);
    }
 
    // 修养修改的对应项id
    function changeCompleted(id,completed){
        todos.forEach(item=>{
            if(id === item.id){
                item.completed = completed;
            }
        });
        // 不能直接传todos,因为对比的时候是浅层对比,还是认为同一个对象,是不会进行更新的,我们应该解构出来返回一个新数组
        setTodos([...todos]);
    }
 
    function remove(id){
        setTodos(todos.filter(item=>item.id !== id));
    }
 
    function removeCompleted(){
        setTodos(todos.filter(item=>!item.completed));
    }
 
    function changeAllCompleted(completed){
        todos.forEach(item=>{
            item.completed = completed;
        });
        setTodos([...todos]);
    }
 
    function editVal(id,val){
        todos.forEach(item=>{
            if(id === item.id){
                item.val = val;
            }
        });
        setTodos([...todos]);
    }
 
    return (<div id="todoapp">
        <Header addTodo = {addTodo}/>
        <Main
            todos={todos}
            changeCompleted={changeCompleted}
            remove={remove}
            editVal={editVal}
            changeAllCompleted = {changeAllCompleted}
        />
        <Footer
            todos={todos}
            removeCompleted = {removeCompleted}
        />
    </div>);
}
 
 
export default Todos;
 

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.06-14
Branch: bhStudy-Hooks

commit description:as0.06-14-example06-14(实现TodoLists——完成全选/全不选功能)

tag:as0.06-14

5.1.15 example06-15

可能有的浏览器会存储历史记录,我们可以手动去掉input记住功能:

code\project\study-hooks\react-app\src\todos\header.js

<input
                id="new-todo"
                type="text"
                placeholder="What needs to be done?"
                value={todo}
                onChange={(e) => {
                        setTodo(e.target.value);
                    }
                }
                autoComplete='off'
                onKeyDown={(e) => {  // 回车事件
                    // 判断如果是回车
                    if (e.keyCode === 13){
                        if (!todo.trim()){
                            alert("输入内容不能为空!");
                            e.target.focus();
                        }else{
                            addTodo(todo);
                            // 清空
                            setTodo('');
                        }
                    }
                }}
            />

参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.06-15
Branch: bhStudy-Hooks

commit description:as0.06-15-example06-15(实现TodoLists——去掉input记住功能)

tag:as0.06-15



(后续待补充)
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值