React Hooks --- useState 和 useEffect
学习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的更多相关文章
- React Hooks: useState All In One
React Hooks: useState All In One useState import React, { useState } from 'react'; function Example( ...
- React Hooks useState为什么顺序很重要
一个Function Component的state状态整体是作为memoizedState存在FIber中的. function执行时,首先取memoizedState第一个base state,作 ...
- react hooks & component will unmount & useEffect & clear up
react hooks & component will unmount & useEffect & clear up useEffect & return === u ...
- 基于react hooks,zarm组件库配置开发h5表单页面
最近使用React Hooks结合zarm组件库,基于js对象配置方式开发了大量的h5表单页面.大家都知道h5表单功能无非就是表单数据的收集,验证,提交,回显编辑,通常排列方式也是自上向下一行一列的方 ...
- react hooks 全面转换攻略(一) react本篇之useState,useEffect
useState 经典案例: import { useState } from 'react'; function Example() { const [count, setCount] = useS ...
- React Hooks实现异步请求实例—useReducer、useContext和useEffect代替Redux方案
本文是学习了2018年新鲜出炉的React Hooks提案之后,针对异步请求数据写的一个案例.注意,本文假设了:1.你已经初步了解hooks的含义了,如果不了解还请移步官方文档.(其实有过翻译的想法, ...
- 从 React 架构开始讲解 useState、useEffect 编程设计
随着前端开发复杂度增加,原生开发模式显得越来越笨重,前端框架也层出不穷. MVC 和 MVVM MVC MVC是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计 ...
- react hooks useEffect 取消 promise
react hooks useEffect 取消 promise cancel promise https://github.com/facebook/react/issues/15006#issue ...
- React Hooks: useEffect All In One
React Hooks: useEffect All In One useEffect https://reactjs.org/docs/hooks-effect.html https://react ...
随机推荐
- js检测手机类型(android,ios,blackberry,windows等)
var isMobile = { Android: function() { return navigator.userAgent.match(/Android/i); }, BlackBerry: ...
- mysql在windows(含客户端工具)及linux(yum)环境下安装
下载 地址:https://dev.mysql.com/downloads/ windows安装包下载 linux安装包下载 https://dev.mysql.com/downloads/mysql ...
- reactnative遇到的问题总结
1.View中出现文本报错,View等标签中不能出现字符串文本,字符串文本需要包在Text中,遇到如下错误 下面是问题代码: let rightTitle = this.props.rightTitl ...
- go实现tcp 服务器
我们将使用 TCP 协议和协程范式编写一个简单的客户端-服务器应用,一个(web)服务器应用需要响应众多客户端的并发请求:Go 会为每一个客户端产生一个协程用来处理请求.我们需要使用 net 包中网络 ...
- 【java异常】Building workspace has encountered a problem. Error
可能是workspace设置错误,检查一下 或者把项目重新下一下,或者重新maven导入
- Layui 新标签打开
原文:https://blog.csdn.net/sr_www/article/details/81394365 layuiAdmin 后台管理模板 iframe版 在新标签中打开网页 / 在ifra ...
- Linux下进程间通信方式——pipe(管道)
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把 ...
- P2827 蚯蚓
目录 题目链接 思路 代码 题目链接 咕咕咕 思路 如果是\(q=0\)的话,相当于维护一个集合,支持查询最大值,删除最大值,添加新值,用\(set\)即可实现 如果是\(q>0\)的话,我们可 ...
- 【DP】【P5615】 [MtOI2019] 时间跳跃
Description 给定 \(n\) 条边,第 \(i\) 条边的长度为 \(i\),每条边都有 \(50\%\) 的概率被选择,求如果选出的边能组成一个平面凸多边形,则方案的权值是方案中边的数量 ...
- 学好Python后可从事岗位+学习Python的难度
一.学好Python好就业: 1.Linux运维.Linux运维是必须而且一定要掌握Python语言,Python可以满足Linux运维工程师的工作需求提升效率,总而提升自己的能力.用Python实现 ...