Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
注意:所有的Hooks都是用use来声明的。
const [state, setState] = useState(initialState);
通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。
考虑到在blog中不好体现代码更改的位置,小迪才用github托管代码,大家可以查看github,看到详细版本修改过程,搭配博客学习。
const [state, setState] = useState(initialState);
react
的state
使用
useState
基本使用。
回顾类式组件,添加一个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;
参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.01-1
Branch: bhStudy-Hookscommit description:as0.01-1-example01-1(react的state使用——添加一个state)
tag:as0.01-1
更新类式组件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-Hookscommit description:as0.01-2-example01-2(react的state使用——更新state)
tag:as0.01-2
仔细观察其实类组件的state
用起来还是比较麻烦的,简单办法就是接下来我们一起学习的hooks
。
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
。
参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.01-3
Branch: bhStudy-Hookscommit description:as0.01-3-example01-3(react的state使用——Hooks中useState的返回值)
tag:as0.01-3
解构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-Hookscommit description:as0.01-4-example01-4(react的state使用——Hooks中实现数据添加和更新)
tag:as0.01-4
但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-Hookscommit description:as0.01-5-example01-5(react的state使用——Hooks中实现数据添加和更新,维护逻辑单一)
tag:as0.01-5
类组件
componentDidMount、componentDidUpdate 和 componentWillUnmount
需要清除的副作用
hooks钩子
对于生命周期的应用。
基本框子: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-Hookscommit description:as0.02-1-example02-1(react的生命周期useEffect使用——基本框子)
tag:as0.02-1
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
事件。
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-Hookscommit description:as0.02-2-example02-2(react的生命周期useEffect使用——input框里的内容与状态关联)
tag:as0.02-2
实现失去焦点切换回文字状态(类式组件实现)
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-Hookscommit description:as0.02-3-example02-3(react的生命周期useEffect使用——实现失去焦点切换回文字状态)
tag:as0.02-3
生命周期函数——组件挂载完毕、更新完毕阶段
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;
组件更新完毕
打印了好几次,因为我们更新了好几次,更新之后组件又会重新渲染。
发现类组件使用生命周期,信息传递比较麻烦加入,建议融入函数式组件。
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-Hookscommit description:as0.02-5-example02-5(react的生命周期useEffect使用——生命周期函数—组件挂载完毕、更新完毕,融入函数式组件)
tag:as0.02-5
生命周期函数——组件卸载完毕(函数式组件)
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-Hookscommit description:as0.02-6-example02-6(react的生命周期useEffect使用——生命周期函数—组件挂载完毕、更新完毕,组件卸载完毕(函数式组件))
tag:as0.02-6
以上主要演示的是类组件的生命周期,现在完全用函数式组件演示生命周期。实际上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-Hookscommit description:as0.02-7-example02-7(react的生命周期useEffect使用——用函数式组件演示生命周期,缺组件即将卸载的周期)
tag:as0.02-7
演示组件挂载完毕、更新完毕,组件即将卸载
,用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-Hookscommit description:as0.02-8-example02-8(react的生命周期useEffect使用——用函数式组件演示生命周期,完成)
tag:as0.02-8
useEffect
意思是副作用,其实就相当于类组件的componentDidMount、componentDidUpdate 和 componentWillUnmount
需要清除的副作用
我们不仅要检测组件状态变化,可能需要检测更具体一些,检测到具体哪个组件变化了?或者组件仅仅在卸载的时候执行等等,如何处理呢?
需求:页面文字很多,会有滚动条,实现input
框始终保持在顶部不随滚动条变化。
框子
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-Hookscommit description:as0.03-1-example03-1(react的生命周期useEffect使用——实现input框始终保持在顶部不随滚动条变化-框子实现)
tag:as0.03-1
监听组件状态变化
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-Hookscommit description:as0.03-2-example03-2(react的生命周期useEffect使用——实现input框始终保持在顶部不随滚动条变化-监听组件状态变化)
tag:as0.03-2
只监听 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
的第二个参数实际是一个数组,数组里是它要监测的值,即可以监测多个值发生变化。
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-Hookscommit description:as0.03-3-example03-3(react的生命周期useEffect使用——实现input框始终保持在顶部不随滚动条变化-监听特定组件状态变化)
tag:as0.03-3
注意不要忽略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-Hookscommit description:as0.03-4-example03-4(react的生命周期useEffect使用——实现input框始终保持在顶部不随滚动条变化-注意不要忽略useEffect第二个参数的设置,否则会出问题。)
tag:as0.03-4
希望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;
然而出现有点跳的情况,可以用绝对定位和固定定位来解决这个问题,这里不是重点也就不解决了。
不过仍然存在问题,我们发现组件卸载了之后,事件仍然在触发。如果观察F12
的Elements
的input
标签实际已经被卸载了,因此这种情况出现,就很容易报错或发生奇异的问题。虽然元素已经从dom
中卸载,但还存在内存中。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SpoR9sp8-1595228445478)(https://cdn.jsdelivr.net/gh/6xiaoDi/blogpic/images/20200709_01/20200709131822.gif)]
参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.03-5
Branch: bhStudy-Hookscommit description:as0.03-5-example03-5(react的生命周期useEffect使用——实现input框始终保持在顶部不随滚动条变化-添加事件监听,仅仅添加一次了,但出了问题。)
tag:as0.03-5
需要清除的副作用
组件卸载之后,需要把事件也删除了。
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-Hookscommit description:as0.03-6-example03-6(react的生命周期useEffect使用——实现input框始终保持在顶部不随滚动条变化-添加事件监听,仅仅添加一次了,组件卸载之后,需要把事件也删除了。)
tag:as0.03-6
如果希望挂载阶段和更新阶段都执行useEffect
,那就别加第二个参数。
第二个参数为空数组,默认只在挂载阶段执行。
如果仅仅希望更新阶段执行,同样也别加第二个参数,但需要再引进一个开关变量来控制。
希望卸载阶段完成,就返回一个函数来进行卸载工作。
上面案例,实际上切换到input
框的时候,焦点实际是自动聚焦上去的(我们这里没有)。这个如何实现呢?需要加上focus
。
获取dom
节点,在类组件中用ref
,到了hooks
用useRef
。
另外它还有别的作用,之前组件更新的时候,可获取上次状态和当前次状态进行对比,但通过useEffect
拿不到之前的状态,如果想获取上次的状态如何处理呢?
这就需要用到了useRef
。
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
。
current
可以设置一个初始值。
function Edit(props){
const {
text,setText,setEdit} = props;
let t = useRef(1);
console.log(t);
......
}
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;
参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.04-1
Branch: bhStudy-Hookscommit description:as0.04-1-example04-1(useRef——useRef最基本作用)
tag:as0.04-1
通过上例发现其实就不需要用原生获取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-Hookscommit description:as0.04-2-example04-2(useRef——焦点是自动聚焦)
tag:as0.04-2
useRef
除此之外它还能保存之前的值或者状态。
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-Hookscommit description:as0.05-1-example05-1(useRef——记录上次值但没有更新)
tag:as0.05-1
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-Hookscommit description:as0.05-2-example05-2(useRef——记录上次值并实时更新)
tag:as0.05-2
这样经常使用的钩子,我们已经说明了基本的功能。我们来做一个案例练习MVVM框架
的入门练习:TodoLists
。
输入内容回车之后,可添加到下面的信息栏。除了这个添加功能之外还有可以选中任务,代表这个任务做完了。还有全选和全不选的功能。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7kwJmc8w-1595228445509)(https://cdn.jsdelivr.net/gh/6xiaoDi/blogpic/images/20200709_01/20200709155517.gif)]
以及删除功能,删除同时会同步全选和全不选的功能,除此之外还可以清除所有已完成的任务。以及单对单条的编辑功能。
我们仔细观察todolist
的静态文件,再移植到react
实现TodoLists
框子实现——对照其静态html
文件复制即可。
将样式抽取出来index.css
。
将其划分为index.js
,下面有header.js、mian.js、footer.js
组件。
mian.js
内容比较多,为了功能独立,我们再单另把li
拿出来封装,偷懒就不再新建js
文件了,就在该文件中新建一个子组件。
但注意组件中style
属性是对象,而不是html
中的字符串(可以先删掉);并且受控组件事件也需要添加;html
中for
属性在组件中为htmlFor
;class
属性在组件中为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;
参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.06-1
Branch: bhStudy-Hookscommit description:as0.06-1-example06-1(实现TodoLists——静态框子)
tag:as0.06-1
在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-Hookscommit description:as0.06-2-example06-2(实现TodoLists——实现在Hear组件中将todo传递给父组件的回调函数)
tag:as0.06-2
在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-Hookscommit description:as0.06-3-example06-3(实现TodoLists——实现添加todos功能)
tag:as0.06-3
实现拿到数据后,来完成后续的主体内容,显示并渲染。
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-Hookscommit description:as0.06-4-example06-4(实现TodoLists——实现部分todos渲染功能)
tag:as0.06-4
接下来把数据关联起来。
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-Hookscommit description:as0.06-5-example06-5(实现TodoLists——实现部分todos数据关联起来)
tag:as0.06-5
将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-Hookscommit description:as0.06-6-example06-6(实现TodoLists——实现部分todos数据对应项的勾选功能)
tag:as0.06-6
实现单个删除功能
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-Hookscommit description:as0.06-7-example06-7(实现TodoLists——实现单个删除功能)
tag:as0.06-7
实现双击选项,出现编辑框,可以修改选项内容。(需要借助生命周期和ref)
实现切换编辑框
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-Hookscommit description:as0.06-8-1-example06-8-1(实现TodoLists——实现双击选项——实现切换编辑框)
tag:as0.06-8-1
失去焦点后,状态可切换回来。
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-Hookscommit description:as0.06-8-2-example06-8-2(实现TodoLists——实现双击选项——实现切换编辑框,出现bug)
tag:as0.06-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);
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-Hookscommit description:as0.06-8-3-example06-8-3(实现TodoLists——实现双击选项——实现切换编辑框,解决切换出现的bug)
tag:as0.06-8-3
完善切换编辑框的编辑内容
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-Hookscommit description:as0.06-9-example06-9(实现TodoLists——完善切换编辑框的编辑内容——但存在bug)
tag:as0.06-9
以上问题:当编辑框内容为空,该选项还挂载页面上。
编辑为编辑框失去焦点后,在生命周期中判断如果内容为空,则不能失去焦点,必须有内容才行,即内容不得为空。
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-Hookscommit description:as0.06-10-example06-10(实现TodoLists——完善切换编辑框的编辑内容——解决无内容失去焦点的bug)
tag:as0.06-10
添加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;
参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.06-11
Branch: bhStudy-Hookscommit description:as0.06-11-example06-11(实现TodoLists——添加footer框子)
tag:as0.06-11
添加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-Hookscommit description:as0.06-12-example06-12(实现TodoLists——完成footer功能)
tag:as0.06-12
完成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-Hookscommit description:as0.06-13-example06-13(实现TodoLists——完成footer功能—完成clear功能)
tag:as0.06-13
实现全选和全不选功能
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-Hookscommit description:as0.06-14-example06-14(实现TodoLists——完成全选/全不选功能)
tag:as0.06-14
可能有的浏览器会存储历史记录,我们可以手动去掉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-Hookscommit description:as0.06-15-example06-15(实现TodoLists——去掉input记住功能)
tag:as0.06-15