从 0 开始学习「React」
简约,没有太多废话,直接干!
参考 —— React 官方中文文档
React 合集 (持续更新):
- 「React」一定要掌握的6个生命周期!
- 「React」props & state & setState
- 「React」牛x的前端这么写 setState()
- 「React」浅析两大框架「DOM diff」
- 「React」浅析 —— 虚拟 DOM
- 「React」函数组件真香!
- 「React」class 组件真难用!
一、什么是 React
React 是用于构建用户界面的 JavaScript 库
- React 用于「构建界面」,专注于视图,主要提供 UI 层的解决方案
- 是一个「JavaScript 库」,并不是一个
框架
,不能解决项目中的所有问题,需要结合其它的库来协助提供完整的解决方案,如:Redux/React-router 等,在这周边生态的配合下,才能组合成一个框架
三大特性
声明式
命令式编程 v.s. 声明式编程
- 命令式编程:用代码告诉「计算机」
去做什么
- 老板:“小王啊,去我办公室,桌上有份文件,打开后拿过来给我”
- 声明式编程:通过代码告诉「计算机」我想要的是什么,让「计算机」
想出如何去做
- PM:“TA 同学,这需要你实现个五彩斑斓的黑”
- 具体 TA 同学怎么去实现这个五彩斑斓的黑,PM 不关心,PM 只关心实现这个五彩斑斓的黑
组件化
函数就是组件,组件就是函数(因为我基本不用 class 组件)
- React 提供了一种全新的语法扩展:JSX
- 创造性地将
渲染逻辑
和UI 逻辑
结合在一起 - 这个结合体在 React 中,就成为组件
- 组件化的出现,大幅度提升了代码的复用率
一次学习,随处编写
学会后,可以在很多地方使用 React 语法来写代码
- 配合
React
写 web 页面 - 配合
React Native
写 手机客户端 APP - 配合
React 360
写 VR界面
二、「起手式」开始使用
从零开始,不多废话
预备知识:
- 已经安装 node,并且会使用 node,包括 npm 或 yarn;
- 了解 ES6 语法
- 会使用 vs code
create-react-app
React 官方能提供的脚手架工具 —— 中文文档
打开终端,敲以下代码:
我使用 yarn
1. 全局安装 create-react-app
yarn global add create-react-app
2. 创建一个 React 项目
create-react-app react-demo
react-demo
是你的项目名,你爱咋命名就咋命名(不要有大写,会给你个报错)
3. 进入项目
cd react-demo/
4. 使用 vs code 打开项目
code .
5. 本地运行项目
yarn start
6. 删除
把无关的东西都删掉,便于入门学习
选中的这些文件,看到没?都给它删咯:
删完之后你看到的应该需要和我一样:
7. 写代码
进入 public\index.html
「复制」以下代码,「粘贴替换」
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>我的第一个 React 项目</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
进入 src\index.js
清空现有代码,「手写」以下代码!
现在页面应该是这样子的:
三、关于 JSX
JSX 是 React 定义的一个 JS
语法扩展
,可以在 JS 里写类似 HTML 的代码,但它「并不是 HTML」
在刚刚手写代码的时候,你就已经写了 JSX 代码!
注意事项
-
注意
className
-
只有一个语法:想写 JS 代码,就用 {} 包起来
标签里面
的所有 JS 代码都要用 {} 包起来- 如果需要变量 n,那么就用 {} 把 n 包起来
- 如果需要对象,那么久用 {} 把对象包起来,如
{{name: 'heycn'}}
-
return 后面习惯写上一个括号,括号包住代码
四、组件
1. 组件 V.S. 元素
- 这是一个 React 元素:
const div = React.createElement('div', ...)
(注意 d 小写) - 这是一个 React 组件:
const Div = () => React.createElement('div', ...)
(注意 D 大写) - 注意大小写,这是一个「不成文的规定」
2. 什么是 React 组件
- 能够跟其他物件
组合
起来的物件
,就是组件 - 组件并没有明确的定义,全靠
感觉理解
- 目前而言,一个返回 React 元素的
函数
,就是组件 - 在 Vue 里,一个
构造选项
就可以表示一个组件
3. React 的两种组件
「函数组件」
我最喜欢的
创建一个函数组件:
function Welcome (props){
return (
<h1> Hello, {props.name} </h1>
)
}
// 或
const Welcome = props => {
return (
<h1> Hello, {props.name} </h1>
)
}
使用方法:
<Welcome name="chennan" />
「类组件」
我比较少用
class Welcome extends React.Component {
render (){
return <h1>Hello, {this.props.name}</h1>
}
}
使用方法:
<Welcome name="chennan" />
4. <Welcome />
会被翻译成什么
「会被翻译成什么」
<div />
会被翻译为React.createElement('div')
,如果是原生的HTML标签
,那么div
就会自动变成字符串<Welcome />
会被翻译为React.createElement(Welcome)
,如果是大写的Welcome
,那就会当做 “函数” 放在这里- 可以去 babeljs.io 看一下怎么翻译的
「React.createElement 的逻辑」
- 如果传入一个
字符串
'div',这会创建一个 div,注意这个 div 是虚拟 DOM
- 如果传入一个
函数
,就会调用该函数,并且获取其返回值 - 如果传入一个
类
,则在类前面加一个new
(这会导致执行 constructor),获取一个组件对象,然后调用对象的render
方法,获取其返回值
5. 类组件和函数组件使用 props (外部数据)
- 类组件直接读取属性
this.props.xxx
- 函数组件直接读取参数
props.xxx
// 代码示例
// other codes...
function App() {
return (
<div>
爸爸
<Son messageForSon='儿子你好' />
</div>
)
}
class Son extends React.Component {
render() {
return (
<div>
我是儿子,我爸对我说「{this.props.messageForSon}」
<Grandson messageForGrandson='孙子你好' />
</div>
)
}
}
const Grandson = props => {
return (
<div>
我是孙子,我爸对我说「{props.messageForGrandson}」
</div>
)
}
// other codes...
6. 类组件和函数组件使用 state (内部数据)
- 类组件用
this.state
读,this.setState
写 - 函数组件用
useState
返回数组,第一项读,第二项写
「类组件使用 state
」
普通写法
// other codes...
class Son extends React.Component {
constructor() {
super()
// 初始化数据
this.state = { n: 0 }
}
add() {
// 写数据用 this.setState 去写
this.setState({ n: this.state.n + 1 })
}
render() {
return (
// 读数据 this.state.n
<div>
儿子 n: {this.state.n}
<button onClick={() => this.add()}>+1</button>
</div>
)
}
}
// other codes...
注意:写数据的时候,最好使用一个新的对象,而不是在原有的对象上进行修改
牛 x 写法
add() {
this.setState(state => {
return { n: state.n += 1 }
})
}
注意:setState 不会马上去改 state 的值,所以最好使用函数去读新的值是最好的
注意事项
-
this.state.n += 1
无效?- 其实
n
已经改变了,只不过 UI 不会自动更新而已 - 调用
setState
才会触发 UI 更新(异步更新) - 因为
React
没有像 Vue 监听 data 一样监听state
- 其实
-
setState
会异步更新 UIsetState
之后,state 不会马上改变,马上读 state 会失败- 所以更推荐的方式是
setState( 这里写函数 )
-
不推荐
this.setState(this.state)
- React 希望我们不要修改旧 state(数据不可变)
-
这是一种函数式的理念
「函数组件使用 state
」
const App = () => {
const [n, setN] = React.useState(0) // 第一项读,第二项写;0 是初始值
return (
<div>
n: {n}
<button onClick={() => setN(n + 1)}>+1</button>
</div>
)
}
函数组件 setN
永远不会改变 n
注意事项
-
跟类组件类似的地方
- 也要通过 setX (新值) 来更新 UI
-
跟类组件不同的地方
- 没有 this,一律用参数和变量
- 爽吧?
7. class 组件 深入浅出
跟上面会有些重复
「ES6 创建 class 组件的方式」
import React from 'react'
class App extends React.Component {
constructor() {
super()
this.state = { n: 0 }
}
render() {
return (
<div>n: {this.state.n}</div>
)
}
}
export default App
「props」
我将它翻译为「外部数据」,更便于理解
props 的作用
-
接受外部数据
- 只能读不能写
- 外部数据由父组件传递
-
接受外部函数
- 在恰当的时候,调用该函数
- 该函数一般是父组件的函数
初始化
class A extends React.Component {
constructor(props) {
super(props)
}
render(){}
}
这样做之后,this.props
就是外部数据 对象的地址
怎么读
// 通过 this.props.xxx 来读
this.props.xxx
语法
class A extends React.Component {
constructor(props) {
super(props)
}
render(){
return <div onClick={this.props.onClick}>
{this.props.name}
<div>
{this.props.children}
</div>
</div>
}
}
示例
class A extends React.Component {
constructor(props) {
super(props)
this.state = { x: 1 }
}
onClick = () => {
this.setState({
x: this.state.x + 1
})
}
render() {
return (
<div>
这是 A
<button onClick={this.onClick}>+1</button>
<B name={this.state.x} />
</div>
)
}
}
class B extends React.Component {
render() {
return (
// 使用 this.props.name 来读
<div>n: {this.props.name}</div>
)
}
}
怎么写
- 永远不要去改
props
! - 外部数据,就应该由外部更新
- 这是 React 的理念!
「state & setState」
我将
state
翻译为「内部数据」
初始化
class A extends React.Component {
constructor(props) {
super(props)
this.state = {
user: {
name: 'heycn',
age: 18
}
}
}
render() {
return ( ... )
}
}
怎么读
// 通过 this.state.xxx 来读
this.state.xxx
怎么写
// 通过 this.setState(???, fn) 来写
this.setState(???, fn)
参考这篇博客:「React」牛x的前端这么写 setState()
推荐第二种方法
方法一
重点看
onClick1
的代码
class A extends React.Component {
constructor(props) {
super(props)
this.state = { x: 1 }
}
onClick1 = () => {
this.setState({ x: this.state.x + 1 })
}
render() {
return (
<div>
x: {this.state.x}
<button onClick={this.onClick1}>+1</button>
</div>
)
}
}
方法二
重点看
onClick2
的代码
class A extends React.Component {
constructor(props) {
super(props)
this.state = { x: 1 }
}
onClick2 = () => {
this.setState(state => ({ x: state.x + 1 }))
}
render() {
return (
<div>
x: {this.state.x}
<button onClick={this.onClick2}>+1</button>
</div>
)
}
}
总结
-
读用
this.state
this.state.xxx.yyy.zzz
-
写用
this.setState(???, fn)
this.setState(newState, fn)
- 注意:
setState
不会立刻改变this.state
,会在当前代码运行之后,再去更新this.state
,从而触发 UI 更新 this.setState((state, props) => newState, fn)
- 这种方式的
state
反而更容易理解 fn
会在写入成功后执行
-
写时会 shallow merge
setState
会自动更新state
与旧state
进行第一层的合并
8. 函数组件 深入浅出
「创建方式」
实现 +1 功能
import React, { useState } from 'react'
const App = props => {
const [n, setN] = useState(0)
const onClick = () => setN(n + 1)
return (
<div>
{n}
<button onClick={onClick}>+1</button>
</div>
)
}
export default App
比 class 组件
简约得多
「没有 state 怎么办」
React v16.8.0 推出 Hooks API,其中 useState 可以解决问题
「没有生命周期钩子怎么办」
React v16.8.0 推出 Hooks API,其中 useEffect 可以解决问题
「模拟 c...DidMount
」
useEffect(() => { console.log('第一次渲染') }, [])
返回的第二个参数为空数组
import React, { useState, useEffect } from 'react'
const App = props => {
const [n, setN] = useState(0)
const onClick = () => setN(n + 1)
useEffect(() => {
console.log('第一次渲染')
}, [])
return (
<div>
{n}
<button onClick={onClick}>+1</button>
</div>
)
}
export default App
「模拟 c...Didupdate」
useEffect(() => { console.log('n 更新了') }, [n])
返回的第二个参数里面写:什么东西更新的时候执行
useEffect(() => { console.log('任意属性更新') })
如果想要任何一个 state 变化都要执行的话,就去掉中括号 []
import React, { useState, useEffect } from 'react'
const App = props => {
const [n, setN] = useState(0)
const [m, setM] = useState(0)
const onClickN = () => setN(n + 1)
const onClickM = () => setM(m + 1)
useEffect(() => {
console.log('n 更新了')
}, [n])
return (
<div>
{n} <button onClick={onClickN}>n+1</button>
<hr />
{m} <button onClick={onClickM}>m+1</button>
</div>
)
}
export default App
「模拟 c...WillUnmount」
useEffect(() => { return () => {console.log('Child 销毁了') } })
在 useEffect
里 return 另外一个函数
import React, { useState, useEffect } from 'react'
const App = props => {
const [childVisible, setChildVisible] = useState(true)
const hide = () => setChildVisible(false)
const show = () => setChildVisible(true)
return (
<div>
{childVisible ? <button onClick={hide}>hide</button> : <button onClick={show}>show</button>}
{childVisible ? <Child /> : null}
</div>
)
}
const Child = props => {
useEffect(() => {
return () => {
console.log('Child 销毁了')
}
})
return (
<div>Child</div>
)
}
export default App
「其他生命周期钩子什么模拟」
-
constructor 函数组件执行的时候,就相当于 constructor
-
shouldComponentUpdate 后面的 React.memo 和 useMemo 可以解决
-
render 函数组件 return 的东西就是 render 的东西
总结: 能用函数组件就用函数组件吧,因为它更简单
五、生命周期钩子
类比以下代码
let div = document.createElement('div')
这是 create/construct 过程div.textContent = 'hi
这是初始化 statedocument.body.appendChild(div)
这是 div 的 mount 过程div.textContent = 'hi2
这是 div 的 update 过程div.remove()
这是 div 的 unmount 过程
同理
- React 组件也有这些过程,我们称为生命周期
- 不过只需记住这几个常用的
1.constructor
在这里初始化
state
2.shouldComponentUpdate
是否需要更新组件
用途
- 它允许我们手动判断是否要进行组件更新,我们可以根据应用场景灵活地设置返回值,以避免不必要的更新(让我们自己确定是否更新)
- 返回
true
表示不阻止 UI 更新 - 返回
false
表示阻止 UI 更新
示例
class App extends React.Component {
constructor(props) {
super(props)
this.state = { n: 1 }
}
onClick = () => {
// onClick 函数里对 n 进行了 +1 -1 操作
this.setState(state => ({ n: state.n + 1 }))
this.setState(state => ({ n: state.n - 1 }))
}
shouldComponentUpdate(newProps, newState) {
if (newState.n === this.state.n) {
return false // 返回 false,表示阻止 render
} else {
return true // 返回 true,表示不阻止 render
}
}
render() {
console.log('render了') // 不加 shouldComponentUpdate 的时候 log 一下看看?
return (
<div>
n: {this.state.n}
<button onClick={this.onClick}>+1</button>
</div>
)
}
}
使用内置 React.PureComponent
使用
React.PureComponent
代替React.Component
PureComponent
会在 render
之前对新旧 state
进行对比(之对比第一层,浅对比),如果对比结果一样,那就不更新
class App extends React.PureComponent {
constructor(props) {
super(props)
this.state = { n: 1 }
}
onClick = () => {
// onClick 函数里对 n 进行了 +1 -1 操作
this.setState(state => ({ n: state.n + 1 }))
this.setState(state => ({ n: state.n - 1 }))
}
render() {
console.log('render了')
return (
<div>
n: {this.state.n}
<button onClick={this.onClick}>+1</button>
</div>
)
}
}
3. render
创建虚拟 DOM
用途
-
展示视图
return (<div>...</div>)
这是虚拟 DOM -
只能有一个根元素
-
如果有两个根元素,就要用
<React.Fragment>
包起来 -
<React.Fragment>
可以缩写成<></>
示例
class App extends React.PureComponent {
constructor(props) {
super(props)
this.state = { n: 1 }
}
onClick = () => {
this.setState(state => ({ n: state.n + 1 }))
}
render() {
return (
<>
n: {this.state.n}
<button onClick={this.onClick}>+1</button>
</>
)
}
}
可以写 if...else...
&& 也可以,|| 也可以
class App extends React.PureComponent {
constructor(props) {
super(props)
this.state = { n: 1 }
}
onClick = () => {
this.setState(state => ({ n: state.n + 1 }))
}
render() {
return (
<>
{this.state.n % 2 === 0 ? <div>偶数</div> : <div>奇数</div>}
<button onClick={this.onClick}>+1</button>
</>
)
}
}
可以写 map
不可以直接写
for
循环,需要用数组,可以用map
来代替
class App extends React.PureComponent {
constructor(props) {
super(props)
this.state = { array: [1, 2, 3] }
}
render() {
return this.state.array.map(n => <div key={n}>{n}</div>)
}
}
记得要写 key
4. componentDidMount
组件已出现在页面
用途
- 在元素插入页面后执行代码,这些代码一般来说应该依赖 DOM
- 比如:获取 div 的高度,就最好在这里写
- 此处可以发起
加载数据
的 AJAX 请求(官方推荐) - 首次渲染会执行此钩子
5. componentDidUpdate
组件已更新
用途
- 在视图更新后执行代码
- 此处也可以发起 AJAX 请求,一般用于
更新数据
- 首次渲染不会执行此钩子
- 在此处
setState
可能会引起无限循环,除非放在 if 里 - 若
shouldComponentUpdate
返回false
,则不触发此钩子
6. componentWillUnmount
组件将要被卸载
用途
- 组件将要
被移出页面然后被销毁
时执行代码 unmount
过的组件不会再次mount
举例
- 如果在
componentDidMount
里监听了window scroll
,那么就要在componentWillUnmount
里取消监听 - 如果在
componentDidMount
里创建了Timer
,那么就要在componentWillUnmount
里取消Timer
- 如果在
componentDidMount
里创建了AJAX
请求,那么就要在componentWillUnmount
里取消请求 - 否则前端页面的内存会很多
- 原则:谁污染谁整治
六、Hooks
有哪些
- useState
- useEffect & useLayoutEffect
- useContext
- useReducer
- useMemo
- useRef
- 自定义 Hook
1. useContext
我把它翻译为
上下文
全局变量
是全局的上下文
上下文
是局部的全局变量
使用方法
- 使用
C = createContext(initial)
创建上下文 - 使用
<C.provider>
圈定作用域 - 在作用域内使用
useContext(C)
来使用上下文
注意事项
- 你在一个模块将 C 里面的值改变
- 另一个模块不会感知到这个变化
2. useEffect & useLayoutEffect
useEffect
副作用(这个 API 名字起得非常不好!)
- 对环境的改变就叫副作用,如修改
document.title = 'hi'
- 但我们不一定非要把副作用放在 useEffect 里
- 实际上叫做 afterRender 更好,因为实在每次 render 后执行
用途
- 作为
componentDidMount
使用,[] 作第二个参数,只会在第一次渲染执行 - 作为
componentDidUpdate
使用,可指定依赖,只会在指定的依赖更新的时候执行
const App = () => {
const [n, setN] = useState(0)
const onClick = () => {
setN(i => i + 1)
}
useEffect(() => {
console.log('第 1 次渲染执行')
}, [])
useEffect(() => {
console.log('任何一个 state 变化都会执行,包括第 1 次')
})
useEffect(() => {
console.log('每次 n 变化都会执行,包括第 1 次')
}, [n])
useEffect(() => {
if (n !== 0) {
console.log(`n 变化时执行,现在 n 是: ${n}`)
}
}, [n])
console.log('------ 分割线 ------')
return (
<div>
n: {n}
<button onClick={onClick}>+1</button>
</div>
)
}
- 作为
componentWillUnmount
使用,通过 return 函数,这个函数会在组件快死掉的时候执行,用来清理现场(清理垃圾)
useEffect(() => {
const id = setInterval(() => {
console.log('hi')
}, 1000)
return () => {
window.clearInterval(id)
}
}, [])
但是目前无法消除,以后项目中会用到
- 以上三种用途可同时存在
特点
- 如果同时存在多个
useEffect
,会按照顺序执行
useLayoutEffect
布局副作用
- useEffect 在浏览器渲染完成后执行(在屏幕像素改变之后执行)
- useLayoutEffect 在浏览器渲染前执行(在屏幕像素改变之前执行,在 DOM 操作之后紧接着执行)
const App = () => {
const [value, setValue] = useState(0)
// 这时网速慢的话,页面会从 value: 0 -> value: 1000
// useEffect(() => {
// document.querySelector('#x').innerText = `value: 1000`
// }, [value])
// 这时候使用 useLayoutEffect 就可以解决 value 从 0 -> 1000 时抖动的情况
useLayoutEffect(() => {
document.querySelector('#x').innerText = `value: 1000`
}, [value])
return (
<div id='x' onClick={() => setValue(0)}>
value: {value}
</div>
)
}
特点
useLayoutEffect
总是比useEffect
先执行useLayoutEffect
里的任务最好是影响了 Layout(最好是影响外观),如果任务是 '上报数据' 这种,就不需要放在这里占用时间了
经验
- 为了用户体验,优先使用
useEffect
,优先 render
3. useMemo
多余的 render
在学习 useMemo 前,首先需要知道 React.memo
const App = () => {
const [n, setN] = useState(0)
const [m, setM] = useState(0)
const onClick = () => {
setN(n => n + 1)
}
return (
<div>
<div>
<button onClick={onClick}>update n {n}</button>
</div>
<Child data={m} />
</div>
)
}
const Child = props => {
console.log('执行了')
console.log('如果这里有大量代码')
return <div>Child: {props.data}</div>
}
上面代码只改变了 n,m 没有改变,但是 Child 还是执行了,但我不想执行 Child
解决方法,使用 React.memo
const App = () => {
const [n, setN] = useState(0)
const [m, setM] = useState(0)
const onClick = () => {
setN(n => n + 1)
}
return (
<div>
<div>
<button onClick={onClick}>update n {n}</button>
</div>
<Child data={m} />
</div>
)
}
// 使用 memo 能解决不必要的 render
const Child = memo(props => {
console.log('执行了')
console.log('如果这里有大量代码')
return <div>Child: {props.data}</div>
})
小结
- 我的 n 变了,m 没变
- 但是依赖于 m 的组件却自动刷新了
- 那么就可以使用 React.memo
- 如果 props 不变,就没有必要再次执行一个函数组件
但是,这里有个 BUG:
const App = () => {
const [n, setN] = useState(0)
const [m, setM] = useState(0)
const onClick = () => {
setN(n => n + 1)
}
const onClickChild = () => {}
return (
<div>
<div>
<button onClick={onClick}>update n {n}</button>
</div>
<Child data={m} onClick={onClickChild} />
</div>
)
}
// 使用 memo 能解决不必要的 render
const Child = memo(props => {
console.log('执行了')
console.log('如果这里有大量代码')
return <div onClick={props.onClick}>Child: {props.data}</div>
})
我在 Child 添加了 onClick,但是函数没有任务,Child 竟然进行了 不必要的 render
!
这是什么呢?
- 因为,当点击
n + 1
的时候 - App() 会重新执行
- 那么第 9 行代码
const onClickChild = () => {}
会重新执行,之前执行的是空函数,现在执行的是新的空函数 - 虽然都是空函数,但是每次执行的都是不一样的空函数
- 那么执行的是不同的空函数,那么就代表 Child 的
onClick
变了 - 那为什么 n 可以?因为 n 是一个数值,数值是相等的,而函数是一个对象
- 第一次
空函数的地址
,和第二次空函数的地址
是不相等的
,即使功能是一样 - 这就是
值
和引用
的区别
如何解决?那我们就需要用到 useMemo
useMemo
可以实现函数的重(chong)用
useMemo 接受一个函数和一个参数,函数的返回值是 你要缓存的东西
,第二个参数是依赖(代码中表示:当 m 变化了,我再执行)
作用
- 用来缓存一些在两次新旧组件迭代的时候
- 希望用上一次的值
- 比如我上面的代码:
- 我希望 m 改变的时候,再执行
- 如果 m 没改变,那我就不执行
const App = () => {
console.log('App 执行了')
const [n, setN] = useState(0)
const [m, setM] = useState(0)
const onClick = () => {
setN(n => n + 1)
}
const onClickChild = useMemo(() => {
return () => {
console.log('我不会执行的')
}
}, [m])
return (
<div>
<div>
<button onClick={onClick}>update n {n}</button>
</div>
<Child data={m} onClick={onClickChild} />
</div>
)
}
const Child = memo(props => {
console.log('执行了')
console.log('如果这里有大量代码')
return <div onClick={props.onClick}>Child: {props.data}</div>
})
特点
- 第一个参数是
() => value
- 第二个参数是依赖
[m]
- 只有当依赖变化时,才会计算出新的
value
- 如果以来不变,那么就重用之前的
value
- 这很像 Vue2 的
computed
注意
- 如果 value 是个函数,那么就要写成一个这么奇怪的东西:
useMemo(() => (x) => console.log('我执行了'))
- 这是一个返回函数的函数
- 于是就有了
useCallback
,它的作用和useMemo
一模一样!
const onClickChild = useCallback(() => {
console.log('我不会执行的')
}, [m])
useMemo((x) => console.log('我执行了'), [m])
等价于useMemo(() => (x) => console.log('我执行了'), [m])
4. useRef
目的
- 如果你需要一个值,在组件不断 render 时
保持不变
,就需要使用到useRef
- 初始化:
const count = useRef(0)
- 读取:
count.current
const App = () => {
const [n, setN] = useState(0)
const count = useRef(0)
const onClick = () => setN(n => n + 8)
useEffect(() => {
count.current += 1
console.log(`执行了 ${count.current} 次`)
})
return (
<div>
n: {n}
<button onClick={onClick}>n+1</button>
</div>
)
}
为什么需要 current
为什么需要
current
来 读/写 它的值?
为了保证两次 useRef
是同一个值(只要引用能做到)
能做到变化时自动 render 吗?
- 不能!
- 因为这不符合 React 的理念(函数式编程)
- React 的理念是:
UI = f(data)
- 你如果想要这个功能,完全可以自己加
- 监听 ref,当 ref.current 变化时,调用 setX 即可
5. forwardRef
- 如果你的函数组件,想要接受别人传来的 ref 参数
- 必须把这个函数用
forwardRef
包起来 - 所以:想用 ref,就必须使用 forwardRef
const App = () => {
const buttonRef = useRef(null)
return (
<div>
<Button ref={buttonRef}>按钮</Button>
</div>
)
}
const Button = forwardRef((props, ref) => {
return <button ref={ref} {...props} />
})
useRef
- 可以用来引用 DOM 对象
- 也可以用来引用普通对象
forwardRef
- 由于 props 不包含 ref,所以需要 forwardRef
- 为什么 props 不包含 ref 呢?因为大部分时候不需要
6. useImperativeHandle
- 对 ref 进行设置
- 大概率不会用到这个 Hook
自定义 Hook
这个功能可能是 React Hooks 里面最牛 x 的功能
自定义 Hook 之 useUpdate
import { useState, useEffect } from 'react'
const useUpdate = (fn, dep) => {
const [count, setCount] = useState(0)
useEffect(() => {
setCount(x => x + 1)
}, [dep])
useEffect(() => {
if (count > 1) {
fn()
}
}, [count, fn])
}
export default useUpdate
最后
- 好了,差不多这样子,就可以找一些小项目练练手了
- 多实践才能提高
完。
转载自:https://juejin.cn/post/7080439852323307556