目录
1.useState和类组件的this.state一样,都是用来管理组件状态的
2.useState的initialState也可以是一个用来生成状态初始值的函数
面试常问的缩写:
SEO :Search Engine Optimization,搜索引擎优化
SPA :single-page application , 单页应用程序
JSONP:JSON with Padding 是一种请求数据的一种方式
JSON:JavaScript Object Notation 一种轻量级的数据交互格式
FSC: Functional Stateless Component 功能无状态的组件
一、Hook
在React框架中,HOOK是最流行的技术 (HOOK:官方提供的回调函数)
所有的hook都只能在 函数组件 中使用,其它普通函数或者类中都不能用
React Hook是React 16.8版本之后添加的新属性,
用最简单的话来说,React Hook就是一些React提供的内置函数,这些函数可以让函数组件和类组件一样能够拥有组件状态(state)以及进行副作用(side effect)
要在合适的时候使用hook,否则会造成性能问题.
(能不用的时候就不能,当遇到性能不好优化的时候,自然会想到使用它)
二、useState
1.useState和类组件的this.state一样,都是用来管理组件状态的
面试题:为什么 函数组件 是无状态的组件
在React Hook没出来之前,函数组件也叫做Functional Stateless Component(FSC:功能无状态的组件),
这是因为函数组件每次执行的时候都会生成新的函数作用域,所以同一个组件的不同渲染(render)之间是不能够共用状态的,
因此开发者一旦需要在组件中引入状态就需要将原来的函数组件改成类组件,这使得开发者的体验十分不好 (面试:具体哪些不友好?)。
解决方案:useState
它允许函数组件将自己的状态持久化到React运行时的某个地方,
这样在组件每次重新渲染的时候都可以从这个地方拿到该状态,而且当该状态被更新的时候,组件也会重渲染。
//语法:
import {useState} from "react"
const [state, setState] = useState(initialState)//数组解构赋值
useState接收一个initialState变量作为状态的初始值,返回值是一个数组。返回数组的第一个元素代表当前state的最新值,第二个元素是一个用来更新state的函数。state和setState这两个变量的命名是你自己取的
state用于组件内部使用的数据
setState函数用于修改state,当修改后会触发所有使用过state的地方重新取值(调用render)
可以用多个useState
useState的返回值是一个数组
数组的0下标的值就是有状态的数据,传入的参数就是0下标的初始数据
1下标就是一个函数,调用这个函数,可以修改0下标的数据,而且刷新页面中使用过0下标的模板
import React ,{useState} from "react"
function App(){
let arr=useState("hello")
function change (){
arr[1]("变了")
}
return (
<div>
<p>{arr[0]}</p>
<button onClick={change}>点击</button>
</div>
)
}
export default App
案例:
import React, { useState } from 'react'
import ReactDOM from 'react-dom'
const App = () => {
//counter是一个变量,setCounter是一个函数
const [counter, setCounter] = useState(0)
const [text, setText] = useState('')
const change1 = (event) => {
setText(event.target.value)
}
return (
<div>
<div>{counter}</div>
//<button onClick={setCounter(counter + 1)} >+</button> //如果这样写,会死循环,因为还没点击就一直在调用这个函数
<button onClick={() => setCounter(counter + 1)} >+</button>
<input onChange={change1} value={text}/>
<div/>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
2.useState的initialState也可以是一个用来生成状态初始值的函数
这种做法主要是避免组件每次渲染的时候initialState需要被重复计算
const [state, setState] = useState(() => {
const initialState = localstorge.getItem("isLogin")
return initialState
})
3.setState是全量替代(面试)
函数组件的setState 和 类组件的this.setState函数的一个重要区别是:
(两者都是修改数据并刷新页面)
类组件是将当前设置的state 浅归并 到 旧state 的操作。
而hook的 setState函数 则是将 新state 直接替换 旧的state。
因此我们在编写函数组件的时候,就要合理划分state,避免将没有关系的状态放在一起管理.
案例:
//不好的设计:
const [state, setState] = useState({ left: 0, top: 0, width: 0, height: 0 })
//由于我们将互不关联的DOM位置信息{left: 0, top: 0}和大小信息{width: 0, height: 0}绑定在同一个state,所以我们在更新任意一个状态的时候也要维护一下另外一个状态:
const handleContainerResize = ({ width, height }) => {
setState({...state, width, height})
}
const handleContainerMove = ({ left, top }) => {
setState({...state, left, top})
}
//正确的设计:
const [position, setPosition] = useState({ left: 0, top: 0 })
const [size, setSize] = useState({ width: 0, height: 0})
const handleContainerResize = ({ width, height }) => {
setSize({width, height})
}
const handleContainerMove = ({ left, top }) => {
setPosition({left, top})
}
//ps:如果你非要将多个互不关联的状态放在一起的话,可以使用useReducer来管理状态,这样你的代码会更好维护。
4.setState没有回调函数 (面试)
无论是useState还是类组件的this.setState都是异步调用的,
也就是说每次组件调用完之后 都不能 立即拿到 最新的state值,进行使用,因为是异步的
为了解决这个问题 ==>
1、类组件的this.setState允许通过一个回调函数来获取到最新的state值:
this.setState(newState, state => { console.log( state)})
2、vue中解决方式是nextTick
3、函数组件的setState函数不存在这么一个可以拿到最新state的回调函数,也没有nextTick
==> 可以使用useEffect来实现相同的效果.
三、useEffect
1.useEffect==>副作用
useEffect是用来 使函数组件 也可以进行副作用操作的
函数的副作用 就是 函数 除了返回值外 对外界环境造成的其它影响。
举个例子,假如我们每次执行一个函数,该函数都会操作全局的一个变量,那么对全局变量的操作就是这个函数的副作用。
2.副作用的分类
而在React的世界里,我们的 副作用大体可以分为两类:
1、调用浏览器的API,例如使用addEventListener来添加事件监听函数等,
2、发起获取服务器数据的请求,例如当用户组件挂载的时候去异步获取用户的信息等。
在Hook出来之前,如果我们需要在组件中进行副作用 ==>
需要将组件写成类组件,然后在组件的生命周期函数里面写副作用,这其实会引起很多代码设计上的问题 (具体问题:面试题),
Hook出来之后,开发者就可以在 函数组件 中使用 useEffect 来定义副作用了。
虽然 useEffect 基本可以覆盖 componentDidMount, componentDidUpdate,componentWillUnmount 等生命周期函数 组合起来使用的所有场景,
但是 useEffect 和 生命周期函数 的设计理念 还是存在本质上的区别的,
如果一味用 生命周期函数 的思考方式去 理解和使用 useEffect 的话,可能会引发一些奇怪的问题
DOM 、BOM、 网络 、 winodw.onload 都是 副作用,就是函数内部的东西影响了外部
3.语法:
import {useEffect} from "react"
useEffect(effect?=>clean, dependencies?)
useEffect的第一个参数effect是要执行的副作用函数,
它可以是任意的用户自定义函数,用户可以在这个函数里面 操作一些浏览器的API或者和外部环境进行交互,网络请求等,这个函数会在 每次组件渲染完成之后被调用,也就是App组件对应的模板加载时运行,也就是mounted之后
useEffect可以有一个返回值,返回一个函数,系统在组件重新渲染之前调用它
第二个参数dependencies来限制该副作用的执行条件一般是属性、状态、函数改变了,就会执行
(第二个参数写[],就是在页面中加载的时候运行)
案例1:useEffect基本用法
import React, { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'
const UserDetail = ({ userId }) => {
const [userDetail, setUserDetail] = useState({})
useEffect(() => {
fetch(`http://ip:port/users/${userId}`)
.then(response => response.json()) //把二进制包转为json
.then(user => setUserDetail(userDetail))
})
return (
<div>
<div>User Name: {userDetail.name}</div>
</div>
)
}
ReactDOM.render(<UserDetail />, document.getElementById('root'))
用户详情信息的副作用会在UserDetail组件每次完成渲染后执行,所以当该组件第一次挂载的时候就会向服务器发起获取用户详情信息的请求然后更新userDetail的值,这里的第一次挂载我们可以想象成类组件的componentDidMount。
死循环:但是组件会不断向服务端发起请求。出现这个死循环的原因是useEffect里面调用了setUserDetail,这个函数会更新userDetail的值,从而使组件重渲染,而重渲染后useEffect的effect继续被执行,进而组件再次重渲染
为了避免重复的副作用执行,useEffect允许我们通过第二个参数dependencies来限制该副作用什么时候被执行:指明了dependencies的副作用,只有在dependencies数组里面的元素的值发生变化时才会被执行,因此如果要避免上面的代码进入死循环我们就要将userId指定为我们定义的副作用的dependencies
案例2:dependencies参数
import React, { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'
const UserDetail = ({ userId }) => {
const [userDetail, setUserDetail] = useState({})
useEffect(() => {
fetch(`https://myapi/users/${userId}`)
.then(response => response.json())
.then(user => setUserDetail(userDetail))
}, [userId])
return (
<div>
<div>User Name: ${userDetail.name}</div>
</div>
)
}
ReactDOM.render(<UserDetail />, document.getElementById('root'))
案例3:effect返回值函数
import React, { useEffect } from 'react'
import ReactDOM from 'react-dom'
const WindowScrollListener = () => {
useEffect(() => {
const handleWindowScroll = () => console.log('正在滚')
window.addEventListener('scroll', handleWindowScroll)
// 这个就是清除函数
return () => {
window.removeEventListener(handleWindowScroll)
}
}, [])
return (
<div>
滚起来
</div>
)
}
ReactDOM.render(<WindowScrollListener />, document.getElementById('root'))
//上面的代码中在WindowScrollListener组件首次渲染完成后注册一个监听页面滚动事件的函数,并在组件下一次渲染前移除该监听函数。由于我们指定了一个空数组作为这个副作用的dependencies,所以这个副作用只会在组件首次渲染时被执行一次,而它的cleanup函数只会在组件unmount时才被执行,这就避免了频繁注册页面的监听函数从而影响页面的性能。
4、面试题
vue中,父组件传子组件
子组件用传过来的值去做网络请求,如果父组件的数据变了,那么子组件怎么写能用到这个新值
==> 不能把网络请求写在mounted中,因为刷新的时候,mounted不会再运行。
可以用 keep-alive的actived
5.总结:
useEffect(() => { }) //每次组件渲染都执行
useEffect(() => { return () => { } }, []) //组件第一次渲染执行,组件移除时触发clean函数
useEffect(() => { return () => { } }, [count]) //count改变才会执行,组件重新渲染前触发clean函数