应用 React 进行我的项目开发也有好几个我的项目了,趁着最近有空来对 React 的常识做一个简略的复盘。
目录概览
- React 是单向数据流还是双向数据流?它还有其余特点吗?
-
setState
- React 通过什么形式来更新数据
- React 不能间接批改 State 吗?
- setState 是同步还是异步的?
- setState 小测
-
React 生命周期
- constructor (构造函数)
- static getDerivedStateFromProps
- shouldComponentUpdate
- render
- getSnapshotBeforeUpdate
- componentDidMount
- componentDidUpdate
- componentWillUnmount
- 生命周期阶段
- 其余生命周期
-
React 组件通信
- React.Context 怎么应用
- 函数组件是什么?与类组件有什么区别?
-
Hooks
- Hook vs class
- Hooks 的应用
- 自定义 Hook 的应用
- Hook 应用束缚
-
class 组件与 Hook 之间的映射与转换
- 生命周期
- Hooks 没有实现的生命周期钩子
- 转换实例变量
- 强制更新 Hook 组件
- 获取旧的 props 和 state
- 受控组件与非受控组件的区别
- Portals 是什么?
React 是单向数据流还是双向数据流?它还有其余特点吗?
React 是单向数据流,数据是从上向下流。它的其余次要特点时:
- 数据驱动视图
- 申明式编写 UI
- 组件化开发
setState
React 通过什么形式来更新数据
React 是通过 setState
来更新数据的。调用多个 setState
不会立刻更新数据,而会批量提早更新后再将数据合并。
除了 setState
外还能够应用 forceUpdate
跳过以后组件的 shouldComponentUpdate
diff,强制触发组件渲染(防止应用该形式)。
React 不能间接批改 State 吗?
- 间接批改 state 不会触发组件的渲染。
- 若间接批改 state 援用的值,在理论应用时会导致谬误的值呈现
- 批改后的 state 可能会被后续调用的
setState
笼罩
setState 是同步还是异步的?
出于性能的思考,React 可能会把多个 setState
合并成一个调用。
React
内有个 batchUpdate(批量更新)
的机制,在 React 能够管制的区域 (如组件生命周期、React 封装的事件处理器) 设置标识位 isBatchingUpdate
来决定是否触发更新。
比方在 React 中注册的 onClick
事件或是 componentDidMount
中间接应用 setState
都是异步的。若想拿到触发更新后的值,能够给 setState
第二个参数传递一个函数,该函数在 数据更新后会触发的回调函数,函数的参数就是更新后最新的值。
不受 React 管制的代码快中应用 setState
是同步的,比方在 setTimeout
或是原生的事件监听器中应用。
setState 小测
输入以下后果:
componentDidMount() {this.setState({ count: this.state.count + 1});
console.log("1 -->", this.state.count);
this.setState({count: this.state.count + 1});
console.log("2 -->", this.state.count);
setTimeout(() => {this.setState({ count: this.state.count + 1});
console.log("3 -->", this.state.count);
}, 0);
setTimeout(() => {this.setState({ count: this.state.count + 1});
console.log("4 -->", this.state.count);
}, 0);
}
输入后果为:
1 --> 0
2 --> 0
3 --> 2
4 --> 3
解答: 调用 setState
后不会立刻更新 state,结尾两次调用会被异步合并调用,因而只有一次调用。一轮事件循环完结后,调用第 3、4 次 setState
。因为在 setTimeout
中调用是同步更新的,因而都能失常的叠加数据。
React 生命周期
React 的生命周期次要是指组件 在特定阶段会执行的函数。以下是 class 组件的局部生命周期图谱:
从上图能够看出:React 的生命周期依照类型划分,可分为 挂载时(Mounting)、更新时(Updating)、卸载时(Unmounting)。图中的生命周期函数成果如下:
constructor (构造函数)
- 触发条件: 组件初始化时
- 是否能够应用
setState
: X - 应用场景: 初始化
state
或者对办法绑定this
static getDerivedStateFromProps
Tips: 不罕用办法
- 触发条件: 调用
render
函数之前 - 是否能够应用
setState
: X - 函数行为: 函数能够返回一个对象用于更新组件外部的
state
数据,若返回null
则什么都不更新。 - 应用场景: 用于 state 依赖 props 的状况,也就是状态派生。值得注意的是派生 state 会导致代码冗余,并使组件难以保护。
shouldComponentUpdate
Tips: 不罕用办法
- 触发条件: 当
props
/state
发生变化 - 是否能够应用
setState
: X - 函数行为: 函数的返回值决定组件是否触发
render
,返回值为true
则触发渲染,反之则阻止渲染。(组件内不写该函数的话,则调用默认函数。默认函数只会返回true
,即只有props
/state
发生变化,就更新组件) - 应用场景: 组件的性能优化,仅仅是浅比拟 props 和 state 的变动的话,能够应用内置的 PureComponent 来代替
Component
组件。
render
- 触发条件: 渲染组件时
- 是否能够应用
setState
: X - 函数行为: 函数的返回值决定视图的渲染成果
- 应用场景 : class 组件中惟一 必须要实现 的生命周期函数。
getSnapshotBeforeUpdate
Tips: 不罕用办法
- 触发条件: 在最近一次渲染输入(提交到 DOM 节点)之前调用
- 是否能够应用
setState
: X - 函数行为: 函数的返回值将传入给
componentDidUpdate
第三个参数中。若只实现了该函数,但没有应用componentDidUpdate
的话,React 将会在控制台抛出正告 - 应用场景: 能够在组件产生更改之前从 DOM 中捕捉一些信息(例如,列表的滚动地位)
componentDidMount
- 触发条件: 组件挂载后(插入 DOM 树中)立刻调用
- 是否能够应用
setState
: Y (能够 间接调用,但会触发额定渲染) - 应用场景: 从网络申请中获取数据、订阅事件等
componentDidUpdate
- 触发条件: 组件更新结束后(首次渲染不会触发)
- 是否能够应用
setState
: Y (更新语句须 放在条件语句 中,不然可能会造成死循环) - 应用场景: 比照新旧值的变动,进而判断是否须要发送网络申请。比方监听路由的变动
componentWillUnmount
- 触发条件: 组件卸载及销毁之前间接调用
- 是否能够应用
setState
: X - 应用场景: 革除 timer,勾销网络申请或革除在
componentDidMount
中创立的订阅等
生命周期阶段
针对 React 生命周期中函数的调用程序,笔者写了一个繁难的 Demo 用于演示: React 生命周期示例
React 组件挂载阶段 先后会触发 constuctor
、static getDerivedStateFromProps
、render
、componentDidMount
函数。若 render
函数内还有子组件存在的话,则会进一步递归:
[Parent]: constuctor
[Parent]: static getDerivedStateFromProps
[Parent]: render
[Children]: constuctor
[Children]: static getDerivedStateFromProps
[Children]: render
[Children]: componentDidMount
[Children]: 挂载阶段完结!
[Parent]: componentDidMount
[Parent]: 挂载阶段完结!
React 组件更新阶段 次要是组件的 props 或 state 发生变化时触发。若组件内还子组件,则子组件会判断是否也须要触发更新。默认状况下 component
组件是只有父组件产生了变动,子组件也会跟着变动。以下是更新父组件 state
数据时所触发的生命周期函数:
[Parent]: static getDerivedStateFromProps
[Parent]: shouldComponentUpdate
[Parent]: render
[Children]: static getDerivedStateFromProps
[Children]: shouldComponentUpdate
[Children]: render
[Children]: getSnapshotBeforeUpdate
[Parent]: getSnapshotBeforeUpdate
[Children]: componentDidUpdate
[Children]: 更新阶段完结!
[Parent]: componentDidUpdate
[Parent]: 更新阶段完结!
值得注意的是: 在本例 Demo 中没有给子组件传参,但子组件也触发了渲染。但从利用的角度上思考,既然你子组件没有须要更新的货色,那就没有必要触发渲染吧?
因而 Component
组件上能够应用 shouldComponentUpdate
或者将 Component
组件替换为 PureComponment
组件来做优化。在生命周期图中也能够看到: shouldComponentUpdate
返回 false
时,将不再持续触发上面的函数。
有时你可能在某些状况下想被动触发渲染而又不被 shouldComponentUpdate
阻止渲染该怎么办呢?能够应用 forceUpdate()
跳过 shouldComponentUpdate
的 diff,进而渲染视图。(须要应用强制渲染的场景较少,个别不举荐这种形式进行开发)
React 组件销毁阶段 也没啥好说的了。父组件先触发销毁前的函数,再逐层向下触发:
[Parent]: componentWillUnmount
[Parent]: 卸载阶段完结!
[Children]: componentWillUnmount
[Children]: 卸载阶段完结!
其余生命周期
除了上图比拟常见的生命周期外,还有一些过期的 API 就没有额定介绍了。因为它们可能在将来的版本会被移除。
- UNSAFE_componentWillMount()
- UNSAFE_componentWillUpdate()
- UNSAFE_componentWillReceiveProps()
当渲染过程,生命周期,或子组件的构造函数中抛出谬误时,会调用如下办法:
- static getDerivedStateFromError()
- componentDidCatch()
React 组件通信
- 父组件通过 props 给子组件传递数据。子组件通过触发父组件提供的回调函数来给父组件传递音讯或数据
React.Context
能够跨层级组件共享数据- 自定义事件
- 引入
Redux
/Mobx
之类的状态管理器
React.Context 怎么应用
Context
能够共享对于组件树而言是全局的数据,比方全局主题、首选语言等。应用形式如下:
-
React.createContext
函数用于生成Context
对象。能够在创立时给Context
设置默认值:const ThemeContext = React.createContext('light');
-
Context
对象中有一个Provider(提供者)
组件,Provider
组件承受一个value
属性用以将数据传递给生产组件。<ThemeContext.Provider value="dark"> <page /> </ThemeContext.Provider>
-
获取
Context
提供的值能够通过contextType
或者Consumer(消费者)
组件中获取。contextType
只能用于类组件,并且只能挂载一个Context
:class MyClass extends React.Component {componentDidMount() { let value = this.context; /* 在组件挂载实现后,应用 MyContext 的值执行一些有副作用的操作 */ } render() { let value = this.context; /* 基于 MyContext 的值进行渲染 */ } } MyClass.contextType = MyContext;
若想给组件挂载多个
Context
, 或者在函数组件内应用Context
能够应用Consumer
组件:<ThemeContext.Consumer> {theme => ( <UserContext.Consumer> {user => (<ProfilePage user={user} theme={theme} /> )} </UserContext.Consumer> )} </ThemeContext.Consumer>
Context
通常实用于传递较为简单的数据信息,若数据太过简单,还是须要引入状态治理(Redux
/Mbox
)。
函数组件是什么?与类组件有什么区别?
函数组件实质上是一个纯函数,它承受 props 属性,最初返回 JSX。
与类组件的差异在于: 它没有实例、不能通过 extends
继承于其余办法、也没有生命周期和 state
。以前函数组件常作为无状态组件,React 16.8+ 能够引入 Hooks
为函数组件反对状态和副作用操作。
Hooks
Hook vs class
类组件的有余:
- 状态逻辑复用难,短少复用机制。渲染属性和高阶组件导致层级冗余。
- 简单组件变得难以了解。
- this 指向令人困扰。
Hooks 的长处:
- 自定义 Hook 不便复用状态逻辑
- 副作用的关注点拆散
- 函数组件没有 this 问题
Hooks 现有的有余:
- 不能齐全取代 class 组件的生命周期,局部不罕用的生命周期临时没有实现。
- Hooks 的运作形式带来了肯定的学习老本,须要转换现有的编程思维,减少了心智累赘。
Hooks 的应用
形容 Hooks 有哪些罕用的办法和大抵用处
useState
: 使函数组件反对设置state
数据,可用于代替类组件的constructor
函数。-
useEffect
: 使函数组件反对操作副作用 (effect) 的能力,Hook 第二个参数是 effect 的依赖项。当依赖项是空时,effect 函数仅会在组件挂载后执行一遍。若有一个或多个依赖项时,只有任意一个依赖项发生变化,就会触发 effect 函数的执行。effect 函数里能够做一些如获取页面数据、订阅事件等操作。除此之外,
useEffect
还能够返回一个函数用于做革除操作,这个革除操作时可选的。罕用于清理订阅事件、DOM 事件等。// 绑定 DOM 事件 useEffect(() => {document.addEventListener('click', handleClick); // useEffect 回调函数的返回值是函数的话,当组件卸载时会执行该函数 // 若没有须要革除的货色,则能够疏忽这一步骤 return () => {document.removeEventListener('click', handleClick); }; }, [handleClick]);
useLayoutEffect
:useEffect
的 effect 执行的机会是在浏览器实现布局和绘制 之后 会提早调用。若想要 DOM 变更的同时同步执行 effect 的话能够应用useLayoutEffect
。它们之间只是执行的机会不同,其余都一样。useContext
: 接管一个Context
对象,并返回Context
的以后值。相当于类组件的static contextType = MyContext
。useReducer
是useState
的代替计划,它的工作形式有点相似于Redux
,通过函数来操作 state。适宜state
逻辑较为简单且蕴含多个子值,或是新的state
依赖于旧的state
的场景。useMemo
次要用于性能优化,它能够缓存变量的值,防止每次组件更新后都须要反复计算值。useCallbck
用于缓存函数,防止函数被反复创立,它是useMemo
的语法糖。useCallback(fn, deps)
的成果相当于是useMemo(() => fn, deps)
。
自定义 Hook 的应用
自定义 Hook 的命名规定是以 use
结尾的函数,比方 useLocalStorage
就合乎自定义 Hook 的命名标准。
应用自定义 Hook 的场景有很多,如表单解决、动画、订阅申明、定时器等等可复用的逻辑都能通过自定义 Hook 来形象实现。
在自定义 Hook 中,能够应用 Hooks 函数将可复用的逻辑和性能提取进去,并将外部的 state
或操作的办法从自定义 Hook 函数中返回进去。函数组件应用时就能够像调用一般函数一祥调用自定义 Hook 函数, 并将自定义 Hook 返回的 state
和操作方法通过解构保留到变量中。
上面是 useLocalStorage 的实现,它将 state 同步到本地存储,以使其在页面刷新后放弃不变。用法与 useState 类似,不同之处在于咱们传入了本地存储键,以便咱们能够在页面加载时默认为该值,而不是指定的初始值。
import {useState} from 'react';
// Usage
function App() {
// Similar to useState but first arg is key to the value in local storage.
const [name, setName] = useLocalStorage('name', 'Bob');
return (
<div>
<input
type="text"
placeholder="Enter your name"
value={name}
onChange={e => setName(e.target.value)}
/>
</div>
);
}
// Hook
function useLocalStorage(key, initialValue) {
// State to store our value
// Pass initial state function to useState so logic is only executed once
const [storedValue, setStoredValue] = useState(() => {
try {
// Get from local storage by key
const item = window.localStorage.getItem(key);
// Parse stored json or if none return initialValue
return item ? JSON.parse(item) : initialValue;
} catch (error) {
// If error also return initialValue
console.log(error);
return initialValue;
}
});
// Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage.
const setValue = value => {
try {
// Allow value to be a function so we have same API as useState
const valueToStore =
value instanceof Function ? value(storedValue) : value;
// Save state
setStoredValue(valueToStore);
// Save to local storage
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
// A more advanced implementation would handle the error case
console.log(error);
}
};
return [storedValue, setValue];
}
留神: 自定义 Hook 函数在定义时,也能够应用另一个自定义 Hook 函数。
Hook 应用束缚
- 只能在 函数组件最顶层 调用 Hook,不能在循环、条件判断或子函数中调用。
- 只能在 函数组件 或者是 自定义 Hook 函数 中调用,一般的 js 函数不能应用。
class 组件与 Hook 之间的映射与转换
函数组件相比 class 组件会短少很多性能,但大多能够通过 Hook 的形式来实现。
生命周期
- constructor:class 组件的构造函数个别是用于初始化
state
数据或是给事件绑定this
指向的。函数组件内没有 this 指向的问题,因而能够疏忽。而state
能够通过useState
/useReducer
来实现。 -
getDerivedStateFromProps:
getDerivedStateFromProps
个别用于在组件 props 发生变化时派生state
。Hooks 实现等同成果如下:function ScrollView({row}) {const [isScrollingDown, setIsScrollingDown] = useState(false); const [prevRow, setPrevRow] = useState(null); if (row !== prevRow) { // Row 自上次渲染以来产生过扭转。更新 isScrollingDown。setIsScrollingDown(prevRow !== null && row > prevRow); setPrevRow(row); } return `Scrolling down: ${isScrollingDown}`; }
-
shouldComponentUpdate: 应用
React.memo
利用到函数组件中后,当 props 发生变化时,会对 props 的新旧值进行前比照,相当于是PureComponent
的性能。如果你还想本人定义比拟函数的话,能够给React.memo
的第二个参数传一个函数,若函数返回true
则跳过更新。const Button = React.memo((props) => {return <button>{props.text}</button> });
- render: 函数组件自身就是一个
render
函数。 -
componentDidMount / componentDidUpdate / componentWillUnmount:
useEffect
第二个参数的依赖项为空时,相当于componentDidMount
,组件挂载后只会执行一次。每个useEffect
返回的函数相当于是componentWillUnmount
等同成果的操作。若有依赖,则 effect 函数相当于是componentDidUpdate
:// 没有依赖项,仅执行一次 useEffect(() => {const subscription = props.source.subscribe(); // 相当于 componentWillUnmount return () => {subscription.unsubscribe(); }; }, []); // 若有依赖项,相当于 componentDidUpdate // 当 page 发生变化时会触发 effect 函数 useEffect(() => {fetchList({ page}); }, [page]);
Hooks 没有实现的生命周期钩子
- getSnapshotBeforeUpdate
- getDerivedStateFromError
- componentDidCatch
转换实例变量
应用 useRef
设置可变数据。
强制更新 Hook 组件
设置一个 没有理论作用 的 state
,而后强制更新 state
的值触发渲染。
const Todo = () => {
// 应用 useState,用随机数据更新也行
const [ignored, forceUpdate] = useReducer(x => x + 1, 0);
function handleClick() {forceUpdate();
}
return <button click={handleClick}> 强制更新组件 </button>
}
获取旧的 props 和 state
能够通过 useRef
来保留数据,因为渲染时不会笼罩掉可变数据。
function Counter() {const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {prevCountRef.current = count;}, []);
const prevCount = prevCountRef.current;
return <h1>Now: {count}, before: {prevCount}</h1>;
}
受控组件与非受控组件的区别
受控组件次要是指表单的值受到 state
的管制,它须要自行监听 onChange
事件来更新 state
。
因为受控组件每次都要编写事件处理器能力更新 state
数据、可能会有点麻烦,React 提供另一种代替计划是 非受控组件。
非受控组件将 实在数据贮存在 DOM 节点 中,它能够为表单项设置默认值,不须要手动更新数据。当须要用到表单数据时再通过 ref
从 DOM 节点中取出数据即可。
留神: 少数状况下 React 举荐编写受控组件。
扩大材料: 受控和非受管制应用场景的抉择
Portals 是什么?
Portals
就像个传送门,它能够将子节点渲染到存在于父组件以外的 DOM 节点的计划。
比方 Dialog
是一个全局组件,依照传统渲染组件的形式,Dialog
可能会受到其容器 css 的影响。因而能够应用 Portals
让组件在视觉上渲染到 <body>
中,使其款式不受 overflow: hidden
或 z-index
的影响。
- 下一篇文章: React 常识回顾 (优化篇)
- 原文出自: React 常识回顾 (应用篇) | Anran758’s blog
- 前端面试笔记: front-end-lab