学习React Hooks, 第一点就是把你以前学习到的React知识全忘掉,从零开始学起。第二点就是要知道React Hooks 都是函数,使用React Hooks,就是调用函数,最后才是每一个hooks的用法。React Hooks只能在函数组件中使用,函数组件也是函数,就是调用和执行。当React调用函数组件进行渲染时,函数组件里面的每一行代码就会依次执行,一个一个的Hooks 也就依次执行。

  useState()

  useState() : 作为函数,它接受一个参数,返回了一个数组。

    参数:可以是任意类型。基本类型, 对象,函数 都没有问题。作用呢?就是给组件设定一个初始的状态。当组件初次渲染的时候,它要显示什么,这个参数就是什么。

    返回值:一个数组。数组的第一项是组件的状态,第二项是更新状态的函数,那么在组件中就可以声明一个变量来保存状态,一个变量来保存更改状态的函数,至此函数组件中就有了状态,确切的说是,组件中拥有一个状态变量,你可以随时更改它的值,组件的状态就是某一时刻的变量的值。更新状态的函数就是用来改变这个变量的值的。

  比如做一个input 输入框,初始状态是空字符串,那么useState 的参数就是"". 返回一个数组,那就声明一个变量,进行接收。从数组中就可以获取状态和更新函数。

function App() {
const arr = useState("");
const state = arr[];
const setState = arr[];
}

  这么写是有点麻烦了,使用数组解构赋值吧,同时为状态变量和更新函数起一个有意义的名字

const App= () => {
const [message, setMessage]= useState( '' );
}

  有了状态变量之后,就可以在函数组件中使用了,完善一下输入框。其时它就是一个普通变量,我们平时怎么使用变量,就怎么使用它。

const App = () => {
const [message, setMessage]= useState(''); function handleChange(e) {
setMessage(e.target.value)
} return (
<input value={message} onChange={handleChange}></input>
)
}

  看看App组件渲染到页面上的过程。初次渲染时,执行第一行代码,调用useState(), 此时useState() 返回了它的参数(空字符串),然后把它赋值给了message, message的值为空字符串, 组件的初始状态为空字符串。 然后继续执行第二行代码,声明了一个函数handleClick,  最后返回一个jsx, 它里面使用了message ,赋值给value, value的值也为空字符串,同时绑定了一个change 事件。 渲染完成后,页面中显示了一个input 输入框,值为空。现在输入一个数,比如1,触发了onChange 事件,它会调用setMessage,  这时触发了React 的更新机制。当然React 不会立刻更新这个值,它会放到更新队列中,和类组件中的setState 一样,React 的渲染是异步的。当真正重新渲染的时候,React 又会调用App函数组件,还是从上到下,一行一行执行代码。先调用useState(), 不过这时useState 返回的不是空字符串(初始值),函数的参数被忽略了,而是返回触发更新的setMessage中的值e.target.value。因为调用setMessage时,我们向React传递了一个参数,React 在内部完成了状态更新。然后把useState返回的值,也就是你在输入框中输入的值1,赋值给了message. 接着向下执行,还是一个函数的创建,然后是jsx,jsx中的message 取当前值为1,然后赋值给value, 渲染完成,页面上input 中显示1。当你再输入2的时候,App 组件再次被调用,还是先执行useSate() 返回12,赋值给message, 然后创建一个handleClick 函数,最后jsx 中message 取12, 组件渲染完成后,页面中的输入框中显示12. 整个过程如下

// 初始渲染。
const message = ''; // useState() 的调用
function handleChange(e) {
setMessage(e.target.value)
}
return (
<input value='' onChange={handleChange}></input>
) // 输入1 更新渲染
const message = ; // useState() 的调用
function handleChange(e) {
setMessage(e.target.value)
}
return (
<input value= onChange={handleChange}></input>
) // 再次输入2,
const message = ; // useState() 的调用
function handleChange(e) {
setMessage(e.target.value)
}
return (
<input value= onChange={handleChange}></input>
)

  你可以发现,组件每一次渲染,都会形成它自己独有的一个版本,在每次渲染中,都拥有着属于它本次渲染的状态和事件处理函数,每一次的渲染都是相互隔离,互不影响的。状态变量,也只是一个普通的变量,甚至在某一次渲染中,可以把它看成一个拥有某个值的常量。它拥用的这个值,正好是react 的useState 提供给我们的。React 负责状态的管理,而我们只是声明变量,使用状态。状态的更新,只不过是组件的重新渲染,React 重新调用了组件函数,生成了一个新的state值。

  这里要注意的一个点是useState的参数,只有在第一次渲染的时候起作用,给状态变量赋初始值,使组件拥有初始状态。在以后的渲染中,不管是调用更新函数导致的渲染,还是父组件渲染导致的它的渲染,参数都不会再使用了,直接被忽略了,组件中的state 状态变量,获取的都是最新值。如果你想像下面的代码一样,使用父组件每次传递过来的props 来更新state,

const Message= (props) => {
const messageState = useState( props.message );
/* ... */
}

  就会有问题,因为props.message, 只会在第一次渲染中使用,以后组件的更新,它就会被忽略了。useState的参数只在初次渲染的时候使用一次,有可能也是useState 可以接受函数的原因,因为有时候,组件初始状态,是需要计算的,比如 我们从localStorage中去取数据作为初始状态。如果在组件中直接写

const Message= (props) => {

let name = localStorage.getItem('name');
const messageState = useState(name);
/* ... */
}

  那么组件每一次的渲染都会调用getItem, 没有必要,因为我们只想获取初始状态,调用一次就够了。useState如果接受函数就可以解决这个问题,因为它的参数,就是只在第一次渲染时才起作用,对于函数来说,就是在第一次渲染的时候,才会调用函数,以后都不会再调用了。

const Message= (props) => {
const messageState = useState(() => {return localstorage.getItem('name')});
/* ... */
}

  还有一个点是更新函数的参数可以是函数,函数参数是前一个状态的值。如果你相使用以前的状态生成一个新的状态,最好使用函数作为更新函数的参数。

function handleChange(e){
const val = e.target.value;
setMessage(prev => prev + val);
}

  当组件的状态是引用类型,比如数组和对象的时候,情况要稍微复杂一点,首先我们不能只更改这个状态变量的属性值,我们要生成一个新的状态值。

const App = () => {
const [messageObj, setMessage] = useState({ message: '' }); // 状态是一个对象 function handleChange(e) {
messageObj.message = e.target.value; // 只是改变状态的属性
setMessage(messageObj)
}
return (
<input type="text" value={messageObj.message} onChange={handleChange}/>
);
};

  你会发现不起作用,无法在输入框中输入内容。React更新状态的时候,它会使用Object.js() 进行比较,如果新的状态和旧的状态相等,它就不会重新渲染。对象的比较是引用的比较,相同的引用, React 不会重新渲染。所以handleChange 要改成如下

 function handleChange(e) {
const newMessageObj = { message: e.target.value }; // 重新生成一个对象
setMessage(newMessageObj);
}

  这又引出了另外一个问题,react 进行状态更新的时候,它会使用新的状态去替换掉老的状态,整体替换。如果组件的状态是多个属性的对象时,可能要出问题。

const App = () => {
const [messageObj, setMessage] = useState({ message: '', id: }); return (
<div>
<input value={messageObj.message}
onChange={e => {
const newMessageObj = { message: e.target.value };
setMessage(newMessageObj);
}}
/>
<p>{messageObj.id} : {messageObj.message}</p>
</div>
);
};

  在输入框中输入内容的时候,发现id 属性不见了。onChange 要修改如下

onChange = { e => {
const val = e.target.value;
setMessage(prevState => {
return { ...prevState, message: val }
});
}}

  也正因为如此,React 建议我们把复杂的状态进行拆分,拆成一个一个单一的变量,更新的时候,只更新其中的某个或某些变量。就是使用多个useState(), 生成多个状态变量和更新函数。

const App = () => {
const [message, setMessage] = useState('');
const [id, setId] = useState(); return (
<div>
<input value={message}
onChange={e => {
setMessage(e.target.value);
}}
/>
<p>{id} : {message}</p>
</div>
);
};

  当然,复杂状态变量(比如,Object 对象)可以拆分,主要是对象的各个属性之间的关联不大。如果对象的各个属性关联性特别强,就必须是一个复杂对象的时候,建议使用useReducer.

  useEffect()

  React 的世界里,不是只有状态和改变状态,它还要和外界进行交互,最常见的就是和服务器进行交互,发送ajax请求。这部分代码放到什么地方呢?使用useEffect(). 组件渲染完成后,你想做什么?就把什么放到useEffect()中,因此,useEffect 的第一个参数就是一个回调函数,包含你要做的事情。组件渲染完成了,要请求数据,那就把请求数据内容放到useEffect 的回调函数中。等到组件真正渲染完成后, 回调函数自动调用,数据请求,就发送出去了。使用一下JSONPlaceholder, 给输入框赋值

import React, { useEffect, useState } from 'react';

export default function App() {
const [message, setMessage]= useState(''); function handleChange(e) {
setMessage(e.target.value)
}
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => {
console.log(json);
setMessage(json.title);
})
}) return <input value={message} onChange={handleChange}></input>
}

  打开控制台,可以发现接口调用了两次,当输入的时候,更是奇怪,直接输入不了,它在不停地调用接口。这时,你可能想到原因了,状态更新会导致组件重新渲染,渲染就会用完成时,完成的那一刹那,useEffect又会重新调用。只要组件渲染完成,不管是初次渲染,还是状态更新导致的重新渲染,useEffect 都会被调用。那不就有问题了吗?请求数据-> 更新状态->重新请求数据->更新状态,死循环了。这就用到了useEffect的第二个参数,一个数组,用来告诉React ,渲染完成后,要不要调用useEffect 中的函数。怎样使用数组进行告知呢?就把useEffect 回调函数中的要用到的外部变量或参数,依次写到数组中。那么React 就知道回调函数的执行是依赖这些变量的,那么它就会时时地监听这些变量的变化,只要有更新,它就会重新调用useEfffect. 这个数组因此也称为依赖数数组,回调函数要再次执行的依赖。现在看一下我们的回调函数fetch,  里面的内容都是写死的,没有任何外部变量依赖,那就写一个空数组。React 看到空数组,也就明白了,useEffect 中的回调函数不依赖任何变量,那它就执行一遍就好了。初次渲染进行执行,以后更新就不用管了。

    useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => {
console.log(json);
setMessage(json.title);
})
}, []) // 空数组,回调函数没有依赖作何外部的变量

  有的时候,不能只获取1(id)的todos, 用户传递出来的id 是几,就要显示id 是几的 todos.  那么fetch的url 就不是固定的了,而是变化的了。useEffect的回调函数也就有了依赖了,那就是一个id,这个id 是需要外界传递过来的,useEffect 的回调函数中用到了一个外部的变量id,那就需要把id写到依赖数组中。再写一个input 表示用户传递过来的id

export default function App() {
const [todoTitle, setTodoTitle]= useState('');
const [id, setId] = useState(); function handleChange(e) {
setTodoTitle(e.target.value)
}
function handleId(e) {
setId(e.target.value);
}
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/todos/' + id)
.then(response => response.json())
.then(json => {
setTodoTitle(json.title);
})
}, [id]) // 回调函数依赖了一个外部变量id return(
<>
<p>id: <input value={id} onChange={handleId}></input></p>
<p>item title: <input value={todoTitle} onChange={handleChange}></input> </p>
</>
)
}

  可以把数组中的id 去掉,测试一下效果,只有初次加载的时候,发送了请求,以后不管你输入什么,再也不会发送请求了。

React Hooks --- useState 和 useEffect的更多相关文章

  1. React Hooks: useState All In One

    React Hooks: useState All In One useState import React, { useState } from 'react'; function Example( ...

  2. React Hooks useState为什么顺序很重要

    一个Function Component的state状态整体是作为memoizedState存在FIber中的. function执行时,首先取memoizedState第一个base state,作 ...

  3. react hooks & component will unmount & useEffect & clear up

    react hooks & component will unmount & useEffect & clear up useEffect & return === u ...

  4. 基于react hooks,zarm组件库配置开发h5表单页面

    最近使用React Hooks结合zarm组件库,基于js对象配置方式开发了大量的h5表单页面.大家都知道h5表单功能无非就是表单数据的收集,验证,提交,回显编辑,通常排列方式也是自上向下一行一列的方 ...

  5. react hooks 全面转换攻略(一) react本篇之useState,useEffect

    useState 经典案例: import { useState } from 'react'; function Example() { const [count, setCount] = useS ...

  6. React Hooks实现异步请求实例—useReducer、useContext和useEffect代替Redux方案

    本文是学习了2018年新鲜出炉的React Hooks提案之后,针对异步请求数据写的一个案例.注意,本文假设了:1.你已经初步了解hooks的含义了,如果不了解还请移步官方文档.(其实有过翻译的想法, ...

  7. 从 React 架构开始讲解 useState、useEffect 编程设计

    随着前端开发复杂度增加,原生开发模式显得越来越笨重,前端框架也层出不穷. MVC 和 MVVM MVC MVC是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计 ...

  8. react hooks useEffect 取消 promise

    react hooks useEffect 取消 promise cancel promise https://github.com/facebook/react/issues/15006#issue ...

  9. React Hooks: useEffect All In One

    React Hooks: useEffect All In One useEffect https://reactjs.org/docs/hooks-effect.html https://react ...

随机推荐

  1. Rotor里的异常处理

    我看到了一些关于Rotor(和CLR)中使用的异常处理机制的问题.下面是关于Rotor异常处理的另一个注意事项列表.目的是帮助Rotor开发人员调试和理解CLR中的异常. 异常生成和抛出 此步骤在很大 ...

  2. STL——list用法总结

    头文件 #include<list> 声明一个int型的list:list<int> a: 1.list的构造函数 list<int>a{1,2,3} list&l ...

  3. MYSQL:基于哈希的索引和基于树的索引有什么区别?

    B+树是一个平衡的多叉树.B+树从根节点到叶子节点的搜索效率基本相当,不会出现大幅波动. 哈希索引采用一定的哈希算法,把键值换成新的哈希值,检索时不需要类似B+树那样从根节点逐级查找,只需一次哈希算法 ...

  4. c# Aes加解密

    using System; using System.Collections.Generic; using System.IO; using System.Security.Cryptography; ...

  5. 微信小程序之简单记账本开发记录(七)

    记账本已经可以实现添加和删除的功能 现在只需要将上述步骤重复一遍便可将另一个界面做出来. 大体上已制作完成,如果在细节上有变动会在这一篇更新 总体来说,这个作业让我对微信小程序的开发有了更多地认识,大 ...

  6. 常用STL使用指北

    常用STL使用指北 set和multiset set和multiset都是基于红黑树(显然是一个二叉搜索树)的STL. 定义 我们可以使用(multi)set<元素类型>名称来定义一个(m ...

  7. 语法上的小trick

    语法上的小trick 构造函数 虽然不写构造函数也是可以的,但是可能会开翻车,所以还是写上吧.: 提供三种写法: ​ 使用的时候只用: 注意,这里的A[i]=gg(3,3,3)的"gg&qu ...

  8. [转]MySQL之——崩溃-修复损坏的innodb:innodb_force_recovery

    原文 https://blog.csdn.net/l1028386804/article/details/77199194 1. mysql崩溃 --------------------------- ...

  9. keepass口令管理实践

    语言修改 加入插件 插件的学习及应用

  10. Expect Command And How To Automate Shell Scripts Like Magic

    In the previous post, we talked about writing practical shell scripts and we saw how it is easy to w ...