自己的电脑做服务区 网站,福州网站建设案例,怎么建设一个音乐网站,做影视网站挣钱吗大家好#xff0c;我是若川。最近组织了源码共读活动#xff0c;感兴趣的可以点此加我微信 ruochuan12 参与#xff0c;每周大家一起学习200行左右的源码#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。React HooksHook 是什么… 大家好我是若川。最近组织了源码共读活动感兴趣的可以点此加我微信 ruochuan12 参与每周大家一起学习200行左右的源码共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。React HooksHook 是什么Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。Hook 是 React 团队在 React 16.8 版本中提出的新特性在遵循函数式组件的前提下为已知的 React 概念提供了更直接的 APIpropsstatecontextrefs 以及声明周期目的在于解决常年以来在 class 组件中存在的各种问题实现更高效的编写 react 组件class 组件的不足难以复用组件间状态逻辑组件状态逻辑的复用需要 props render和高阶组件等解决方案但是此类解决方案的抽象封装将会导致层级冗余形成“嵌套地狱”难以维护复杂组件许多不相干的逻辑代码被混杂在同一个生命周期中相关联的逻辑代码被拆分到不同声明周期当中容易遗忘导致产生bug组件常常充斥着状态逻辑的访问和处理不能拆分为更小的粒度可通过状态管理库集中管理状态但耦合了状态管理库又会导致组件复用性降低this 指向问题在 JavaScript 中class 的方法默认不会绑定 this当调用 class 的方法时 this 的值为 undefined为了在方法中访问 this 则必须在构造器中绑定或使用 class fields 语法实验性语法class Example extends React.Component {constructor(props) {...// 方式1: 在构造函数中绑定 thisthis.handleClick this.handleClick.bind(this);}handleClick() {this.setState({...})}// 方式2: 使用 class fields 语法handleClick () {this.setState({...})}
}难以对 class 进行编译优化由于 JavaScript 历史设计原因使用 class 组件会让组件预编译过程中变得难以进行优化如 class 不能很好的压缩并且会使热重载出现不稳定的情况Hook 的优势Hook 使你在无需改变组件结构的情况下复用状态逻辑自定义 HookHook 将组件中互相关联的部分拆分成更小的函数比如设置订阅或请求数据Hook 使你在非 class 的情况下可以使用更多的 React 特性Hook 使用规则Hook 就是 Javascript 函数使用它们时有两个额外的规则只能在函数外层调用 Hook不要在循环、条件判断或者子函数中调用只能在 React 的函数组件和自定义 Hook 中调用 Hook。不要在其他 JavaScript 函数中调用在组件中 React 是通过判断 Hook 调用的顺序来判断某个 state 对应的 useState的所以必须保证 Hook 的调用顺序在多次渲染之间保持一致React 才能正确地将内部 state 和对应的 Hook 进行关联useStateuseState 用于在函数组件中调用给组件添加一些内部状态 state正常情况下纯函数不能存在状态副作用通过调用该 Hook 函数可以给函数组件注入状态 stateuseState 唯一的参数就是初始 state会返回当前状态和一个状态更新函数并且 useState 返回的状态更新函数不会把新的 state 和旧的 state 进行合并如需合并可使用 ES6 的对象结构语法进行手动合并const [state, setState] useState(initialState);方法使用import React, { useState } from react;export default function Counter() {const [count, setCount] useState(0);return (divbutton onClick{() setCount(count - 1)}-/buttoninput typetext value{count} onChange{(e) setCount(e.target.value)} /button onClick{() setCount(count 1)}/button/div);
}等价 class 示例useState 返回的状态类似于 class 组件在构造函数中定义 this.state返回的状态更新函数类似于 class 组件的 this.setStateimport React from react;export default class Counter extends React.Component {constructor(props) {super(props);this.state {count: 0};}render() {return (divbutton onClick{() this.setState({ count: this.state.count - 1 })}-/buttoninput typetext value{this.state.count} onChange{(e) this.setState({ count: e.target.value })} /button onClick{() this.setState({ count: this.state.count 1 })}/button/div);}
}函数式更新如果新的 state 需要通过使用先前的 state 计算得出可以往 setState 传递函数该函数将接收先前的 state并返回一个更新后的值import React, { useState } from reactexport default function Counter() {const [count, setCount] useState(0);const lazyAdd () {setTimeout(() {// 每次执行都会最新的state而不是使用事件触发时的statesetCount(count count 1);}, 3000);} return (divpthe count now is {count}/pbutton onClick{() setCount(count 1)}add/buttonbutton onClick{lazyAdd}lazyAdd/button/div);
}惰性初始 state如果初始 state 需要通过复杂计算获得则可以传入一个函数在函数中计算并返回初始的 state此函数只会在初始渲染时被调用import React, { useState } from reactexport default function Counter(props) {// 函数只在初始渲染时执行一次组件重新渲染时该函数不会重新执行const initCounter () {console.log(initCounter);return { number: props.number };};const [counter, setCounter] useState(initCounter);return (divbutton onClick{() setCounter({ number: counter.number - 1 })}-/buttoninput typetext value{counter.number} onChange{(e) setCounter({ number: e.target.value})} /button onClick{() setCounter({ number: counter.number 1 })}/button/div);
}跳过 state 更新调用 State Hook 的更新函数时React 将使用 Object.is 来比较前后两次 state如果返回结果为 trueReact 将跳过子组件的渲染及 effect 的执行import React, { useState } from react;export default function Counter() {console.log(render Counter);const [counter, setCounter] useState({name: 计时器,number: 0});// 修改状态时传的状态值没有变化则不重新渲染return (divp{counter.name}: {counter.number}/pbutton onClick{() setCounter({ ...counter, number: counter.number 1})}/buttonbutton onClick{() setCounter(counter)}/button/div);
}useEffect在函数组件主体内React 渲染阶段改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性useEffect Hook 的使用则是用于完成此类副作用操作。useEffect 接收一个包含命令式、且可能有副作用代码的函数useEffect函数会在浏览器完成布局和绘制之后下一次重新渲染之前执行保证不会阻塞浏览器对屏幕的更新useEffect(didUpdate);方法使用import React, { useState, useEffect } from react;export default function Counter() {const [count, setCount] useState(0);// useEffect 内的回调函数会在初次渲染后和更新完成后执行// 相当于 componentDidMount 和 componentDidUpdateuseEffect(() {document.title You clicked ${count} times;});return (divpcount now is {count}/pbutton onClick{() setCount(count 1)}/button/div);
}等价 class 示例useEffect Hook 函数执行时机类似于 class 组件的 componentDidMount、componentDidUpdate 生命周期不同的是传给 useEffect 的函数会在浏览器完成布局和绘制之后进行异步执行import React from react;export default class Counter extends React.Component {constructor(props) {super(props);this.state {count: 0,};}componentDidMount() {document.title You clicked ${this.state.count} times;}componentDidUpdate() {document.title You clicked ${this.state.count} times;}render() {return (divpcount now is {this.state.count}/pbutton onClick{() this.setState({ count: this.state.count 1 })}/button/div);}
}清除 effect通常情况下组件卸载时需要清除 effect 创建的副作用操作useEffect Hook 函数可以返回一个清除函数清除函数会在组件卸载前执行。组件在多次渲染中都会在执行下一个 effect 之前执行该函数进行清除上一个 effect清除函数的执行时机类似于 class 组件componentDidUnmount 生命周期这的话使用 useEffect 函数可以将组件中互相关联的部分拆分成更小的函数防止遗忘导致不必要的内存泄漏import React, { useState, useEffect } from react;export default function Counter() {const [count, setCount] useState(0);useEffect(() {console.log(start an interval timer)const timer setInterval(() {setCount((count) count 1);}, 1000);// 返回一个清除函数在组件卸载前和下一个effect执行前执行return () {console.log(destroy effect);clearInterval(timer);};}, []);return (divpcount now is {count}/pbutton onClick{() setCount(count 1)}/button/div);
}优化 effect 执行默认情况下effect 会在每一次组件渲染完成后执行。useEffect 可以接收第二个参数它是 effect 所依赖的值数组这样就只有当数组值发生变化才会重新创建订阅。但需要注意的是确保数组中包含了所有外部作用域中会发生变化且在 effect 中使用的变量传递一个空数组作为第二个参数可以使 effect 只会在初始渲染完成后执行一次import React, { useState, useEffect } from react;export default function Counter() {const [count, setCount] useState(0);useEffect(() {document.title You clicked ${count} times;}, [count]); // 仅在 count 更改时更新return (divpcount now is {count}/pbutton onClick{() setCount(count 1)}/button/div);
}useContextContext 提供了一个无需为每层组件手动添加 props 就能在组件树间进行数据传递的方法useContext 用于函数组件中订阅上层 context 的变更可以获取上层 context 传递的 value prop 值useContext 接收一个 context 对象React.createContext的返回值并返回 context 的当前值当前的 context 值由上层组件中距离当前组件最近的 MyContext.Provider 的 value prop 决定const value useContext(MyContext);方法使用import React, { useContext, useState } from react;const themes {light: {foreground: #000000,background: #eeeeee},dark: {foreground: #ffffff,background: #222222}
};// 为当前 theme 创建一个 context
const ThemeContext React.createContext();export default function Toolbar(props) {const [theme, setTheme] useState(themes.dark);const toggleTheme () {setTheme(currentTheme (currentTheme themes.dark? themes.light: themes.dark));};return (// 使用 Provider 将当前 props.value 传递给内部组件ThemeContext.Provider value{{theme, toggleTheme}}ThemeButton //ThemeContext.Provider);
}function ThemeButton() {// 通过 useContext 获取当前 context 值const { theme, toggleTheme } useContext(ThemeContext);return (button style{{background: theme.background, color: theme.foreground }} onClick{toggleTheme}Change the buttons theme/button);
}等价 class 示例useContext(MyContext) 相当于 class 组件中的 static contextType MyContext 或者 MyContext.ConsumeruseContext 并没有改变消费 context 的方式它只为我们提供了一种额外的、更漂亮的、更漂亮的方法来消费上层 context。在将其应用于使用多 context 的组件时将会非常有用import React from react;const themes {light: {foreground: #000000,background: #eeeeee},dark: {foreground: #ffffff,background: #222222}
};const ThemeContext React.createContext(themes.light);function ThemeButton() {return (ThemeContext.Consumer{({theme, toggleTheme}) (button style{{background: theme.background, color: theme.foreground }} onClick{toggleTheme}Change the buttons theme/button)}/ThemeContext.Consumer);
}export default class Toolbar extends React.Component {constructor(props) {super(props);this.state {theme: themes.light};this.toggleTheme this.toggleTheme.bind(this);}toggleTheme() {this.setState(state ({theme:state.theme themes.dark? themes.light: themes.dark}));}render() {return (ThemeContext.Provider value{{ theme: this.state.theme, toggleTheme: this.toggleTheme }}ThemeButton //ThemeContext.Provider)}
}优化消费 context 组件调用了 useContext 的组件都会在 context 值变化时重新渲染为了减少重新渲染组件的较大开销可以通过使用 memoization 来优化假设由于某种原因您有 AppContext其值具有 theme 属性并且您只想在 appContextValue.theme 更改上重新渲染一些 ExpensiveTree方式1: 拆分不会一起更改的 contextfunction Button() {// 把 theme context 拆分出来其他 context 变化时不会导致 ExpensiveTree 重新渲染let theme useContext(ThemeContext);return ExpensiveTree className{theme} /;
}当不能拆分 context 时将组件一分为二给中间组件加上 React.memofunction Button() {let appContextValue useContext(AppContext);let theme appContextValue.theme; // 获取 theme 属性return ThemedButton theme{theme} /
}const ThemedButton memo(({ theme }) {// 使用 memo 尽量复用上一次渲染结果return ExpensiveTree className{theme} /;
});返回一个内置 useMemo 的组件function Button() {let appContextValue useContext(AppContext);let theme appContextValue.theme; // 获取 theme 属性return useMemo(() {// The rest of your rendering logicreturn ExpensiveTree className{theme} /;}, [theme])
}useReduceruseReducer 作为 useState 的代替方案在某些场景下使用更加适合例如 state 逻辑较复杂且包含多个子值或者下一个 state 依赖于之前的 state 等。使用 useReducer 还能给那些会触发深更新的组件做性能优化因为父组件可以向自组件传递 dispatch 而不是回调函数const [state, dispatch] useReducer(reducer, initialArg, init);方法使用import React, { useReducer } from reactconst initialState { count: 0 };function reducer(state, action) {switch (action.type) {case increment:return {count: state.count 1};case decrement:return {count: state.count - 1};default:throw new Error();}
}export default function Counter() {const [state, dispatch] useReducer(reducer, initialState);return (pCount: {state.count}/pbutton onClick{() dispatch({type: decrement})}-/buttonbutton onClick{() dispatch({type: increment})}/button/);
}初始化 stateuseReducer 初始化 sate 的方式有两种// 方式1
const [state, dispatch] useReducer(reducer,{count: initialCount}
);// 方式2
function init(initialClunt) {return {count: initialClunt};
}const [state, dispatch] useReducer(reducer, initialCount, init);useRefuseRef 用于返回一个可变的 ref 对象其 .current 属性被初始化为传入的参数initialValueuseRef 创建的 ref 对象就是一个普通的 JavaScript 对象而 useRef() 和自建一个 {current: ...} 对象的唯一区别是useRef 会在每次渲染时返回同一个 ref 对象const refContainer useRef(initialValue);绑定 DOM 元素使用 useRef 创建的 ref 对象可以作为访问 DOM 的方式将 ref 对象以 div ref{myRef} / 形式传入组件React 会在组件创建完成后会将 ref 对象的 .current 属性设置为相应的 DOM 节点import React, { useRef } from reactexport default function FocusButton() {const inputEl useRef(null);const onButtonClick () {inputEl.current.focus();};return (input ref{inputEl} typetext /button onClick{onButtonClick}Focus the input/button/);
}绑定可变值useRef 创建的 ref 对象同时可以用于绑定任何可变值通过手动给该对象的.current 属性设置对应的值即可import React, { useState, useRef, useEffect } from react;export default function Counter() {const [count, setCount] useState(0);const currentCount useRef();// 使用 useEffect 获取当前 countuseEffect(() {currentCount.current count;}, [count]);const alertCount () {setTimeout(() {alert(Current count is: ${currentCount.current}, Real count is: ${count});}, 3000);}return (pcount: {count}/pbutton onClick{() setCount(count 1)}Count add/buttonbutton onClick{alertCount}Alert current Count/button/);
}性能优化useCallback useMemouseCallback 和 useMemo 结合 React.Memo 方法的使用是常见的性能优化方式可以避免由于父组件状态变更导致不必要的子组件进行重新渲染useCallbackuseCallback 用于创建返回一个回调函数该回调函数只会在某个依赖项发生改变时才会更新可以把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染的子组件在 props 属性相同情况下React 将跳过渲染组件的操作并直接复用最近一次渲染的结果import React, { useState, useCallback } from react;function SubmitButton(props) {const { onButtonClick, children } props;console.log(${children} updated);return (button onClick{onButtonClick}{children}/button);
}
// 使用 React.memo 检查 props 变更复用最近一次渲染结果
SubmitButton React.memo(submitButton);export default function CallbackForm() {const [count1, setCount1] useState(0);const [count2, setCount2] useState(0);const handleAdd1 () {setCount1(count1 1);}// 调用 useCallback 返回一个 memoized 回调该回调在依赖项更新时才会更新const handleAdd2 useCallback(() {setCount2(count2 1);}, [count2]);return (divpcount1: {count1}/pSubmitButton onButtonClick{handleAdd1}button1/SubmitButton/divdivpcount2: {count2}/pSubmitButton onButtonClick{handleAdd2}button2/SubmitButton/div/)
}useCallback(fn, deps) 相当于 useMemo(() fn, deps)以上 useCallback 可替换成 useMemo 结果如下const handleAdd2 useMemo(() {return () setCount2(count2 1);
}, [count2]);useMemo把“创建”函数和依赖项数组作为参数传入 useMemo它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算使用注意传入 useMemo 的函数会在渲染期间执行不要在这个函数内部执行与渲染无关的操作如果没有提供依赖项数组useMemo 在每次渲染时都会计算新的值import React, { useState, useMemo } from react;function counterText({ countInfo }) {console.log(${countInfo.name} updated);return (p{countInfo.name}: {countInfo.number}/p);
}
// // 使用 React.memo 检查 props 变更复用最近一次渲染结果
const CounterText React.memo(counterText);export default function Counter() {const [count1, setCount1] useState(0);const [count2, setCount2] useState(0);const countInfo1 {name: count1,number: count1};// 使用 useMemo 缓存最近一次计算结果会在依赖项改变时才重新计算const countInfo2 useMemo(() ({name: count2,number: count2}), [count2]);return (divCounterText countInfo{countInfo1} /button onClick{() setCount1(count1 1)}Add count1/button/divdivCounterText countInfo{countInfo2} /button onClick{() setCount2(count2 1)}Add count2/button/div/);
}其他 HookuseImperativeHandleuseImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 React.forwardRef 一起使用import React, { useRef, useImperativeHandle, useState } from reactfunction FancyInput(props, ref) {const inputRef useRef();// 自定义暴露给父组件的 ref 实例值useImperativeHandle(ref, () ({focus: () {inputRef.current.focus();}}));return input ref{inputRef} typetext {...props} /;
}
// 通过 forwardRef 向父组件传递暴露的 ref
const ForwardFancyInput React.forwardRef(FancyInput);export default function Counter() {const [text, setText] useState();const inputRef useRef();const onInputFocus () {inputRef.current.focus();};return (ForwardFancyInput ref{inputRef} value{text} onChange{e setText(e.target.value)} /button onClick{onInputFocus}Input focus/button/);
}useLayoutEffectuseLayoutEffect 与 useEffect 类似与 useEffect 在浏览器 layout 和 painting 完成后异步执行 effect 不同的是它会在浏览器布局 layout 之后painting 之前同步执行 effectuseLayoutEffect 的执行时机对比如下import React, { useState, useEffect, useLayoutEffect } from react;export default function LayoutEffect() {const [width, setWidth] useState(100px);// useEffect 会在所有 DOM 渲染完成后执行 effect 回调useEffect(() {console.log(effect width: , width);});// useLayoutEffect 会在所有的 DOM 变更之后同步执行 effect 回调useLayoutEffect(() {console.log(layoutEffect width: , width);});return (div idcontent style{{ width, background: red }}内容/divbutton onClick{() setWidth(100px)}100px/buttonbutton onClick{() setWidth(200px)}200px/buttonbutton onClick{() setWidth(300px)}300px/button/);
}// 使用 setTimeout 保证在组件第一次渲染完成后执行获取到对应的 DOM
setTimeout(() {const contentEl document.getElementById(content);// 监视目标 DOM 结构变更会在 useLayoutEffect 回调执行后useEffect 回调执行前调用const observer new MutationObserver(() {console.log(content element layout updated);});observer.observe(contentEl, {attributes: true});
}, 1000);自定义Hook通过自定义 Hook可以将组件逻辑提取到可重用的函数中在 Hook 特性之前React 中有两种流行的方式来共享组件之间的状态逻辑render props和高阶组件但此类解决方案会导致组件树的层级冗余等问题。而自定义 Hook 的使用可以很好的解决此类问题创建自定义 Hook自定义 Hook 是一个函数其名称以 “use” 开头函数内部可以调用其他的 Hook。以下就是实时获取鼠标位置的自定义 Hook 实现import { useEffect, useState } from reactexport const useMouse () {const [position, setPosition] useState({x: null,y: null});useEffect(() {const moveHandler (e) {setPosition({x: e.screenX,y: e.screenY});};document.addEventListener(mousemove, moveHandler);return () {document.removeEventListener(mousemove, moveHandler);};}, []);return position;
}使用自定义 Hook自定义 Hook 的使用规则与 Hook 使用规则基本一致以下是 useMouse 自定义 Hook 的使用过程import React from react;
import { useMouse } from ../hooks/useMouse;export default function MouseMove() {const { x, y } useMouse();return (pMove mouse to see changes/ppx position: {x}/ppy position: {y}/p/);
}每次使用自定义 Hook 时React 都会执行该函数来获取独立的 state 和执行独立的副作用函数所有 state 和副作用都是完全隔离的参考文献[React Hooks 官方文档](https://reactjs.org/docs/hooks-intro.html)[详解 React useCallback useMemo](https://juejin.cn/post/6844904101445124110)[Preventing rerenders with React.memo and useContext hook](https://github.com/facebook/react/issues/15156)[MutationObserver MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver)[useLayoutEffect和useEffect的区别](https://zhuanlan.zhihu.com/p/348701319)················· 若川简介 ·················你好我是若川毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》20余篇在知乎、掘金收获超百万阅读。从2014年起每年都会写一篇年度总结已经写了7篇点击查看年度总结。同时最近组织了源码共读活动帮助3000前端人学会看源码。公众号愿景帮助5年内前端人走向前列。识别上方二维码加我微信、拉你进源码共读群今日话题略。分享、收藏、点赞、在看我的文章就是对我最大的支持