首页
统计
留言
友链
关于我
Search
1
git常用命令
6 阅读
2
docker常用命令整理
6 阅读
3
西门子S7-200 SMART在仿真软件运行
6 阅读
4
电脑高清壁纸网站
6 阅读
5
iframe父子窗口通信
4 阅读
前端
JavaScript
React
Vue
Nuxt3
后端
移动端
开发工具
VSCode
版本控制
WebStorm
运维
Docker
电气工程
登录
Search
标签搜索
前端
JavaScript
西门子S7-200 SMART
Vue
React
vscode
Git
运维
Docker
nuxt
Svg
WebStorm
壁纸
Flutter
小熊维尼
累计撰写
19
篇文章
累计收到
3
条评论
首页
栏目
前端
JavaScript
React
Vue
Nuxt3
后端
移动端
开发工具
VSCode
版本控制
WebStorm
运维
Docker
电气工程
页面
统计
留言
友链
关于我
搜索到
6
篇与
的结果
2024-12-09
解决在Vue项目中修改Svg颜色不生效
原因是svg源代码中fill设置了固定颜色解决方案,将fill设置成currentColorfill="currentColor"
2024年12月09日
4 阅读
0 评论
1 点赞
2024-12-06
Vue常见面试题
vue-router导航守卫有哪些?全局前置钩子:beforeEach、beforeResolve、afterEach路由独享守卫:beforeEnter组件内的守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave为什么Vue3.0采用了Proxy,抛弃了Object.defineProperty?Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue2是通过递归+遍历data对象来实现对数据的监控的。如果属性值也是对象那么就需要深度遍历,显然如果能劫持一个完整的对象才是更好的选择。Proxy可以劫持整个对象,并返回一个新的对象。Proxy不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。V-for为什么要加key?如果不使用key,Vue会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/服用相同类型元素的算法。key是Vue中vnode的唯一标记,通过这个key,可以让diff操作可以更准确、更快速。如何从真实DOM到虚拟DOM?涉及到Vue中的模板编译原理,主要过程:将模板转换成ast树,ast用对象来描述真实的JS语法(将真实DOM转换成虚拟DOM)优化树将ast树生成代码为什么Vue采用异步渲染?Vue是组件级更新,如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染,所以为了性能,Vue会在本轮数据更新后,在异步更新视图。核心思想nextTick。为什么Vue组件中的data必须是一个函数?对象为引用类型,当复用组件时,由于数据对象都指向同一个data对象,当在一个组件中修改data时,其他重用的组件中的data也会被修改。而使用返回对象的函数,由于每次返回的都是一个新对象,引用地址不同,则不会出现这个问题。MVC和MVVM的区别?MVCmvc全名是Model View Controller,是模型-视图-控制器的缩写,一种软件设计典范Model(模型):是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。View(视图):是应用程序中处理数据显示的部分,通常视图是依据模型数据创建的。Controller(控制器):是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据,mvc的思想:一句话描述就是Controller负责将Model的数据用View显示出来,换句话就是在Controller里面把Model的数据赋值给View。MVVMmvvm新增了vm类ViewModel层:做了两件事达到了数据的双向绑定,将模型转化成视图,即将后端传递额数据转化成所看到的页面。实现的方式是:数据绑定。将视图转化成模型,即将所看到的页面转化成后端的数据。实现的方式是:DOM事件监听。mvvm与mvc的最大区别就是:它实现了View和Model的自动同步,也就是当Model的属性改变时,我们不用再自己手动操作DOM元素,来改变View的显示,而是改变属性后该属性对应View层显示会自动改变。Vue data中某一个属性的值发生改变后,视图会立即同步执行重新渲染吗?不会立即同步执行重新渲染。Vue实现响应式并不是数据发生变化后DOM立即变化,而是按一定的策略进行DOM的更新。Vue在更新DOM时是异步执行的。只要侦听到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。Vue的优点轻量级框架,只关注视图层,是一个构建数据的视图集合简单易学双向数据绑定组件化视图、数据、结构分离,使数据的更改更为简单虚拟DOM运行速度更快nextTick是什么?Vue实现响应式并不是在数据发生变化后立即更新DOM,使用nextTick是在下次DOM更新循环结束之后立即执行延迟回调。在修改数据之后使用则可以在回调中获取更新后的DOM。Vue的生命周期beforeCreate:实例初始化之后,数据观测之前调用created:实例创建完成之后调用beforeMount:挂载之前调用mounted:挂载完成调用beforeUpdate:数据更新之前调用updated:数据更新之后调用beforeDestroy:实例销毁之前调用destroyed:实例销毁之后调用v-if和v-show的共同点和不同点?共同点:都能控制元素的显示和隐藏。不同点:v-if是动态的向DOM树内添加或者删除DOM元素,v-show是通过display:none来控制元素的显示和隐藏。如何让CSS只在当前组件中起作用?在组件中的style标签中加上scoped<keep-alive></keep-alive>的作用是什么?keep-alive是Vue内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。Vue中常用的指令和它的用法v-model 双向数据绑定v-for 循环v-if v-show 显示与隐藏v-on 绑定事件v-once 只绑定一次vue-loader是什么? 使用它的用途有哪些?vue文件的一个解析器,将template、js、style转换成js模块。用途:js可以写生es6,style样式可以scss或less,template可以加jade等。v-model的使用v-model用于表单数据的双向绑定,其实它就是一个语法糖,背后做了两个操作:v-bind绑定一个value属性,v-on指令给当前元素绑定input事件。分别简述computed和watch的使用场景computed:当一个属性受多个属性影响的时候就需要用到computed。watch:当一条数据影响多条数据的时候就需要用watch。v-on可以监听多个方法吗?可以,用{}对象的写法。渐进式框架的理解主张最少,可以根据不同的需求选择不同的层级。v-if和v-for的优先级v-if和v-for一起使用时,v-for比v-if的优先级更高,这意味着v-if将分别重复运行于每个v-for循环中,所以不推荐v-if和v-for同时使用。Vue常用修饰符.stop 等同于JavaScript中的event.stopPropagation(),防止事件冒泡.prevent 等同于JavaScript中的event.preventDefault(),防止执行预设的行为(标签的默认行为).capture 与事件冒泡的方向相反,事件捕获由外到内.self 只会触发自己范围内的事件,不包含子元素.once 指挥触发一次Vue的两个核心点数据驱动,组件系统数据驱动:ViewModel,保证数据和视图的一致性。组件系统:应用类UI可以看作全部是由组件书构成的。SPA首屏加载慢如何解决?安装动态懒加载所需插件,使用动态懒加载。使用CDN资源。axios的特点有哪些?支持Promise API可以拦截请求和响应转换请求数据和响应数据取消请求自动换成json封装Vue组件的过程?建立组件的模板,先把架子搭起来,写写样式,考虑好组件的基本逻辑。准备好组件的数据输入。即分析好逻辑,定好props里面的数据,类型。准备好组件的数据输出。即根据组件逻辑,做好要暴漏出来的方法。params和query的区别query在浏览器中显示参数,params不显示参数。query刷新页面之后不会丢失数据。params刷新页面会丢失数据。Vue初始化页面闪动的问题在Vue初始化之前,由于div是不归Vue管的,所有在代码还没有解析的情况下会容易出现花屏现象。解决方案:在css里加上 [v-cloak] { display:none },如果没有彻底解决问题,则在根元素加上加上 style="display:none"Vue更新数组是触发视图更新的方法push()、pop()、shift()、unshift()、splice()、sort()、reverse()什么是Vue生命周期? 有什么作用?每个Vue实例在被创建时都要经过一系列的初始化过程,例如:需要设置数据监听、编译模板、将实例挂载到DOM并在数据变化时更新DOM等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这个用户在不同阶段添加自己代码的机会,方便用户做一些逻辑处理。$route和$router的区别$router是VueRouter的实例,在script标签中想要导航到不同的URL,使用$router.push方法等。$route为当前router跳转对象,里面可以获取到当前路由的各种参数信息。VueX有哪几种属性?有5中,分别是state、getter、mutation、action、modulestate:基本数据,数据源存放地getters:从基本数据派生出来的数据mutations:提交更改数据的方法,同步更新action:像一个装饰器,包裹mutations,实现数据的异步更新modules:模块化VueX
2024年12月06日
1 阅读
0 评论
0 点赞
2024-12-06
JavaScript宏任务和微任务简单理解
宏任务 - macro task,又称为task,包括:script代码块I/OxhrsetTimeoutsetIntervalsetImmediateUI交互事件postMessage浏览器为了能够使得JS引擎跟UI引擎有序配合执行,会在每一个宏任务执行结束后,在下一个宏任务执行前,对页面进行重新渲染,执行流程为macro task(JS引擎) -> render(UI引擎) -> macro task(JS引擎)微任务 - micro task包括:Promise.thenawaitprocess.nextTickMutaionObserver执行流程为:micro task -> micro task -> ...注意:微任务总是在宏任务执行过程中产生的任务,它不能单独存在(微任务实际上是宏任务的一个步骤),可以理解为宏任务是微任务寄生的环境在同一个层级的任务中,微任务执行优先级要高于宏任务的优先级,如果不考虑同一个层级,那么比较微任务和宏任务的执行级别毫无意义在宏任务执行过程中产生的微任务,会在该宏任务结束前一次执行完毕微任务实际是宏任务的一个步骤,所有微任务会在下一个宏任务之前全部执行完毕在微任务执行过程中产生的微任务会直接添加到微任务队列的末端,并在下一个宏任务执行之前全部执行掉如果在微任务执行过程中产生的宏任务,则会进入到宏任务队列末尾,按照宏任务顺序在后面的事件循环中执行process.nextTick会在当前执行站的末尾,下一次Event loop之前出发回调函数,也就是说,它指定的任务总是发生在所有异步任务之前setTimeout单线程运行机制,同一时间只能做一件事。不管怎样,都是要等主线线程的流程中兴完毕后才会进行,且按照setTimeout设置的顺序进行排队执行。该函数指定的任务为宏任务,优先级低于process.nextTick()process.nextTick()nodejs的一个异步执行函数,效率比setTimeout更高,执行顺序要早于setTimeout,在主逻辑的末尾任务队列调用之前执行。setInterval()setInterval定时器函数,按照指定的周期不断调用函数。等主线程执行完毕后调用。和setTimeout执行时间一致时,按照设置的顺序来执行。process.nextTick和PromiseNode执行完所有同步任务,接下来就会执行process.nextTick的任务队列。如果你希望异步任务尽可能快地执行,那就使用process.nextTick。根据语言规格,Promise对象的回调函数,会进入异步任务里面的“微任务”(microtask)队列。微任务队列追加在process.nextTick队列的后面,也属于本轮循环。process.nextTick跟setImmediate区别可以把process.nextTick理解为往已经存在的事件队列头部插入任务,而setImmediate可以理解为吧任务添加到已经存在事件队列的末端。
2024年12月06日
4 阅读
0 评论
0 点赞
2024-12-06
JavaScript中的防抖和节流函数
防抖函数防抖是指在事件被触发n秒后再执行回调,如果在这n秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次重复请求。应用场景按钮提交场景:防止多次提交按钮,只执行最后提交的一次服务端验证场景:表单验证需要服务端配合,只窒执行一段连续的输入事件的最后一次,还有搜索联想词功能类似生存环境请用lodash.debounce防抖函数的实现function debounce(fn, wait) { let timer = null; return function() { let context = this,args = [...arguments]; // 如果此时存在定时器的话,则取消之前的定时器重新计时 if (timer) { clearTimeout(timer); timer = null; } // 设置定时器,使事件间隔指定时间后执行 timer = setTimeout(() => { fn.apply(context, args); }, wait) } }节流函数节流是指规定一个单位事件,在这个单位时间内,只能有一次触发事件的回调函数执行,如果同一个单位时间内某事件被触发多次,只能由一次能生效。节流可以使用在scroll函数的事件监听上,通过事件节流来降低事件调用频率。应用场景拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动缩放场景:监控浏览器resize动画场景:避免短时间内多次触发动画引起性能问题节流函数的实现时间戳版function throttle(fn, delay) { let preTime = Date.now(); return function() { let context = this,args = [...arguments],nowTime = Date.now(); // 如果两次时间间隔超过了指定时间,则执行函数。 if (nowTime - preTime >= delay) { preTime = Date.now(); return fn.apply(context, args); } } }定时器版function throttle(fun, wait) { let timeout = null; return function() { let context = this; let args = [...arguments]; if (!timeout) { timeout = setTimeout(() => { fun.apply(context, args) timeout = null }, wait) } } }
2024年12月06日
1 阅读
0 评论
0 点赞
2024-12-06
React Hooks详解
useStateimport { useState } from 'react'; function Example() { const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }useState会返回一个数组,一个state,一个用于更新state的函数useState唯一的参数就是初始state,在初始化渲染期间,返回的状态与传入的第一个值相同注意事项React假设当你多次调用useState的时候,你能保证每次渲染时它们的调用顺序是不变的。因为react是根据每个useState定义时的顺序来确定你在更新state值时更新的是哪个state你可以在事件处理函数中或其他一些地方调用这个函数。它类似class组件的this.setState,但是它不会把新的state和旧的state进行合并,而是直接替换initialState参数只会在组件的初始化渲染中起作用,后续渲染时会被忽略Hook内部使用Object.is来比较新/旧state是否相等,与class组件中的setState方法不同,如果你修改状态的时候,传的状态值没有变化,则不重新渲染useState不能直接存储函数或函数组件,他会调用该函数并且将函数的返回值作为最终state值进行存储或更新。如果必须这么做可以作为一个数组的元素或对象的某个属性进行存储useState没有设置类似class组件中的setState回调函数来拿到最新state然后进行后续操作,可以通过useEffect并设置相应依赖来实现。因为useEffect就是在渲染完成后调用的useState在异步操作中的状态不同步的问题函数的运行时独立的,每个函数都有一份独立的作用域。函数的变量时保存在运行时的作用域里面。当我们有一部操作的时候,经常会碰到异步回调的变量引用是之前的,也就是旧的(这里也可以理解成闭包)。比如下面的一个例子:import React, { useState } from "react"; const Counter = () => { const [counter, setCounter] = useState(0); const onAlertButtonClick = () => { setTimeout(() => { alert("Value: " + counter); }, 3000); }; return ( <div> <p>You clicked {counter} times.</p> <button onClick={() => setCounter(counter + 1)}>Click me</button> <button onClick={onAlertButtonClick}> Show me the value in 3 seconds </button> </div> ); }; export default Counter;当你点击Show me the value in 3 secondes后,紧接着点击Click me使得counter的值从0变成1.三秒后,定时器触发,但alert出来的是0(旧值),但我们希望的结果是当前的状态1这个问题在class component不会出现,因为class component的属性和方法都存在一个instance上,调用方式是:this.state.xxx和this.method()。因为每次都是从一个不变的instance上进行取值,所以不存在引用是旧的问题。除了setTimout这种异步,还有类似事件监听函数(比如滚动监听的回调函数)中访问State也会是旧的这个问题目前的普遍解决方案是使用useRefuseEffect什么是react中的副作用操作?指那些没有发生在数据向视图转换过程中的逻辑,如ajax请求、访问原生dom元素、本地持久化缓存、绑定/解绑事件、添加订阅、设置定时器、记录日志等。副作用操作可以分两类:需要清除的(例如事件绑定/解绑)和不需要清除的。原先在函数组件内(这里指在react渲染阶段)改变dom、发送ajax请求以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的bug并破坏UI的一致性一个需求实现:需要实时让document.title显示你最新的点击次数(count)class组件实现:class Counter extends React.Component{ state = {number:0}; add = ()=>{ this.setState({number:this.state.number+1}); }; componentDidMount(){ this.changeTitle(); } componentDidUpdate(){ this.changeTitle(); } changeTitle = ()=>{ document.title = `你已经点击了${this.state.number}次`; }; render(){ return ( <> <p>{this.state.number}</p> <button onClick={this.add}>+</button> </> ) } }因为需要实时让document.title显示你最新的点击次数(count),所以就必须在componentDidMount或componentDidUpdate中编写重复的代码来重新设置document.titlehooks实现:import React,{Component,useState,useEffect} from 'react'; import ReactDOM from 'react-dom'; function Counter(){ const [number,setNumber] = useState(0); // useEffect里面的这个函数会在第一次渲染之后和更新完成后执行 // 相当于 componentDidMount 和 componentDidUpdate: useEffect(() => { document.title = `你点击了${number}次`; }); return ( <> <p>{number}</p> <button onClick={()=>setNumber(number+1)}>+</button> </> ) } ReactDOM.render(<Counter />, document.getElementById('root'));useEffect就是一个Effect Hook,给函数组件增加了操作副作用的能力。它跟class组件中的componentDidMount、componentDidUpdate和ComponentWillUnmount具有相同的用途,只不过被合并成了一个API与componentDidMount或componentDidUpdate不同,使用useEffect调度的effect不会阻塞浏览器更新屏幕,这让你的应用看起来相应更快。大多数情况下,effect不需要同步地执行。在个别情况下(例如测量布局),有单独的useLayoutEffect Hook供你使用,其API与useEffect相同清除副作用function Counter(){ let [number,setNumber] = useState(0); let [text,setText] = useState(''); // 相当于componentDidMount 和 componentDidUpdate useEffect(()=>{ console.log('开启一个新的定时器') let $timer = setInterval(()=>{ setNumber(number=>number+1); },1000); // useEffect 如果返回一个函数的话,该函数会在组件卸载和更新时调用 // useEffect 在执行副作用函数之前,会先调用上一次返回的函数 // 如果要清除副作用,要么返回一个清除副作用的函数 return ()=>{ console.log('destroy effect'); clearInterval($timer); } }); // },[]);//要么在这里传入一个空的依赖项数组,这样就不会去重复执行 return ( <> <input value={text} onChange={(event)=>setText(event.target.value)}/> <p>{number}</p> <button>+</button> </> ) }useEffect接收一个函数,该函数会在组件渲染到屏幕之后才执行,该函数有要求:要么返回一个能清除副作用的函数,要么旧不返回任何内容默认情况下,useEffect在第一次渲染之后和每次更新之后都会执行。而useEffect接收的函数参数所返回的清除副作用的函数则会在组件更新和卸载前执行,然后更新后执行useEffect接收的函数。然后等待下一次组件更新或卸载,执行副作用的函数...如此循环往复跳过effect进行性能优化默认情况下,useEffect在每次更新之后都会执行有时候,我们只想useEffect只在组件挂在时执行,然后卸载时执行清除副作用函数,不想更新时也执行useEffect(比如原生事件的绑定/解绑)或者只想让执行state发生更新时才执行useEffect(如果某些state更新后拿到最新state进行后续操作)此时,你可以通知react跳过对effect的调用,只要传递数组作为useEffect的第二个可选参数即可如果想执行只运行一次的effect(仅在组件挂在和卸载时执行),可以传递一个空数组作为第二个参数。这就告诉react你的effect不依赖props或state中的任何值,所以它永远都不需要重复执行如果指定state发生更新时才执行useEffect,可以传递一个包含执行state元素的数组作为第二个参数function Counter(){ let [number,setNumber] = useState(0); let [text,setText] = useState(''); // 相当于componentDidMount 和 componentDidUpdate useEffect(()=>{ console.log('useEffect'); let $timer = setInterval(()=>{ setNumber(number=>number+1); },1000); },[text]);// 数组表示 effect 依赖的变量,只有当这个变量发生改变之后才会重新执行 efffect 函数 return ( <> <input value={text} onChange={(event)=>setText(event.target.value)}/> <p>{number}</p> <button>+</button> </> ) }注意:无论你是否制定了useEffct的第二个参数,useEffect永远都会在组件挂载时执行一次使用多个useEffectuseEffect可以声明多个,react将按照effect声明的顺序依次调用组件中的每一个effect我们可以根据具体副作用操作的性质分类将不同种类的操作放到多个useEffect中// Hooks 版 function FriendStatusWithCounter(props) { const [count, setCount] = useState(0); //dom操作相关的effect useEffect(() => { document.title = `You clicked ${count} times`; }); const [isOnline, setIsOnline] = useState(null); //订阅/取消订阅的相关effect useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); // ... }useEffect不能接收async作为回调函数在useEffect中发起异步请求是很常见的场景,由于异步请求通常都是封装好的异步方法,所以新手很容易写成下面这样function App() { const [data, setData] = useState({ hits: [] }); // 注意 async 的位置 // 这种写法,虽然可以运行,但是会发出警告 // 每个带有 async 修饰的函数都返回一个隐含的 promise // 但是 useEffect 函数有要求:要么返回清除副作用函数,要么就不返回任何内容 useEffect(async () => { const result = await axios( 'https://hn.algolia.com/api/v1/search?query=redux', ); setData(result.data); }, []); return ( <ul> {data.hits.map(item => ( <li key={item.objectID}> <a href={item.url}>{item.title}</a> </li> ))} </ul> ); }更优雅的写法:function App() { const [data, setData] = useState({ hits: [] }); useEffect(() => { // 更优雅的方式 //将异步请求封成一个独立async函数然后在useEffect中调用 const fetchData = async () => { const result = await axios( 'https://hn.algolia.com/api/v1/search?query=redux', ); setData(result.data); }; fetchData(); }, []); return ( <ul> {data.hits.map(item => ( <li key={item.objectID}> <a href={item.url}>{item.title}</a> </li> ))} </ul> ); }useLayoutEffect与useEffect的区别useEffect是异步非阻塞调用,useLayoutEffect是同步阻塞调用useEffect在浏览器绘制后调用(即Renderer commit阶段结束后),useLayoutEffect在DOM变更(React的更新)后,浏览器绘制前完成所有操作(即Renderer commit阶段的Layout子阶段同步执行)使用场景:大部分情况下使用useEffect即可。针对小部分特殊情况如短时间内出发了多次状态更新导致渲染多次以致屏幕闪烁的情况,使用useLayoutEffect会在浏览器渲染之前同步更新DOM数据,哪怕是多次的操作,也会在渲染前一次性处理完,再交给浏览器绘制。这样不会导致闪屏现象发生。useReduceruseReducer和redux中的reducer很像。useState内部就是靠useReducer来实现的useReducer接收两个参数:reducer函数含(preState和action两个参数)和初始化的state // 官方 useReducer Demo // 第一个参数:应用的初始化 const initialState = {count: 0}; // 第二个参数:state的reducer处理函数 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(); } } function Counter() { // 返回值:最新的state和dispatch函数 const [state, dispatch] = useReducer(reducer, initialState); return ( <> // useReducer会根据dispatch的action,返回最终的state,并触发rerender Count: {state.count} // dispatch 用来接收一个 action参数「reducer中的action」,用来触发reducer函数,更新最新的状态 <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); }选择useReducer还是useState如果你的页面state很简单,可以直接使用useState如果你的页面state比较复杂(state是一个对象或者state非常多散落在各处)请使用useReducer对于复杂的state操作逻辑(比如某项操作同时操作或更新多个state值),嵌套的state的对象,推荐使用useReducer如果你的页面组件层级比较深,并且需要子组件触发state的变化,可以考虑useReducer+useContextuseContext接收一个context对象(React.createContext的返回值)并返回context的当前值。当前的context值由上层组件中距离当前组件最近的<MyContext.Provider>的value prop决定当组件上层最近的<MyContext.Provider>更新时,该Hook会触发重渲染,并使用最新传递给MyContext provider的context value值useContext(MyContext)相当于class组件中的static contextType = MyContext或者<MyContext.Consumer>const themes = { light: { foreground: "#000000", background: "#eeeeee" }, dark: { foreground: "#ffffff", background: "#222222" } }; const ThemeContext = React.createContext(themes.light); function App() { return ( <ThemeContext.Provider value={themes.dark}> <Toolbar /> </ThemeContext.Provider> ); } function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); } function ThemedButton() { const theme = useContext(ThemeContext); return ( <button style={{ background: theme.background, color: theme.foreground }}> I am styled by theme context! </button> ); }使用useContext和useReducer模拟实现简易ReduxProvider组件import React, { useReducer } from 'react' import './App.css' import ComponentA from './components/ComponentA' import ComponentB from './components/ComponentB' import ComponentC from './components/ComponentC' const initialState = 0 const reducer = (state, action) => { switch (action) { case 'increment': return state + 1 case 'decrement': return state - 1 case 'reset': return initialState default: return state } } export const CountContext = React.createContext() function App() { const [count, dispatch] = useReducer(reducer, initialState) return ( <CountContext.Provider value={{ countState: count, countDispatch: dispatch }} > <div className="App"> {count} <ComponentA /> <ComponentB /> <ComponentC /> </div> </CountContext.Provider> ) } export default AppComponent A://组件A function ComponentA() { const countContext = useContext(CountContext) return ( <div> Component A {countContext.countState} <button onClick={() => countContext.countDispatch('increment')}>Increment</button> <button onClick={() => countContext.countDispatch('decrement')}>Decrement</button> <button onClick={() => countContext.countDispatch('reset')}>Reset</button> </div> ) }Component B:function ComponentB() { const countContext = useContext(CountContext) return ( <div> Component B {countContext.countState} <button onClick={() => countContext.countDispatch('increment')}>Increment</button> <button onClick={() => countContext.countDispatch('decrement')}>Decrement</button> <button onClick={() => countContext.countDispatch('reset')}>Reset</button> </div> ) }useRefuseRef返回一个可变的ref对象,其.current属性被初始化入被传入的参数(initialValue)。返回的ref对象在组件的整个生命周期内不变useRef返回的ref对象在组件的整个生命周期内保持不变,也就是说每次重新渲染函数组件时,返回的ref对象都是同一个。也就是说,useRef的更新不会引起当前组件或子组件的更新渲染但使用React.createRef,每次重新渲染组件都会重新创建ref示例:用useRef存储dom节点function Child() { const inputRef = useRef(); console.log('input===inputRef', input === inputRef); input = inputRef; function getFocus() { inputRef.current.focus(); } return ( <> <input type="text" ref={inputRef} /> <button onClick={getFocus}>获得焦点</button> </> ) }用useRef解决useState异步更新不同步的问题:针对上面useState谈到的异步更新不同步的问题,用useRef返回的immutable RefObject(把值保存在current属性上)来保存state,你可以把useRef存储的值堪称class组件实例中通过this存储的属性。然后取值方式从counter变成了->counterRef.current。如下:import React, { useState, useRef, useEffect } from "react"; const Counter = () => { const [counter, setCounter] = useState(0); const counterRef = useRef(counter); const onAlertButtonClick = () => { setTimeout(() => { alert("Value: " + counterRef.current); }, 3000); }; useEffect(() => { counterRef.current = counter; }); return ( <div> <p>You clicked {counter} times.</p> <button onClick={() => setCounter(counter + 1)}>Click me</button> <button onClick={onAlertButtonClick}> Show me the value in 3 seconds </button> </div> ); }; export default CounterReact.forwardRef在useRef出来之前,由于函数组件是没有实力的,所以函数组件无法使用ref属性来获取dom引用,而对应的解决方法就是React.forwardRef:TextInput函数组件:const TextInput = forwardRef((props,ref) => { //设置input标签node节点作为TextInput组件的ref引用 return <input ref={ref}></input> })function TextInputWithFocusButton() { // 关键代码 const inputEl = useRef(null); const onButtonClick = () => { // 关键代码,`current` 指向已挂载到 DOM 上的文本输入元素 inputEl.current.focus(); }; return ( <> // 用useRef存储TextInput设置的ref引用 <TextInput ref={inputEl}></TextInput> <button onClick={onButtonClick}>Focus the input</button> </> );上面例子说明forwardRef和useRef配合,可以在父组件中操作子组件的ref对象useCallbackuseCallback缓存一个函数,这个函数如果是由父组件作为props传递给子组件,或者自定义hooks里面的函数(通常自定义hooks里面的函数不会依赖于引用它的组件里面的数据),这时候我们可以考虑缓存这个函数,好处:不用每次重新声明新的函数,避免释放内存,分配的计算资源浪费子组件不会因为这个函数的变动重新渲染。(和React.memo搭配使用)function Example(){ const [count, setCount]= useState(1); const getNum = useCallback(() => { return (555 * 666666 )+count //只有count值改变时,才会重新计算 },[count]) const Child = React.memo(({getNum}) =>{ return <h4>总和{getNum()}</h4> }) return <div> <Child getNum={getNum}/> <button onClick={() => setCount(count + 1)}>+1</button> </div> }上面例子,将一个函数交给useCallBack处理并且作为props传递给memo包裹的子组件并子组件调用该方法,定义只有当coutn变化时才会触发子组件重新渲染因为通过useCallBack包裹后的函数通过props传递给子组件的永远是该函数的调用useMemouseMemo主要用于渲染过程优化,两个参数依次是计算函数(通常是组件函数)和依赖状态列表,当依赖的状态发生改变时,才会触发计算函数的执行。如果没有指定依赖,则每一次渲染过程都会执行该计算函数。useMemo的返回值就是计算函数的返回值function Example(){ const [count, setCount]= useState(1); const getNum = useMemo(() => { return (555 * 666666 )+count //只有count值改变时,才会重新计算 },[count]) return <div> <h4>总和:{getNum}</h4> <button onClick={() => setCount(count + 1)}>+1</button> </div> } export default Example;上面例子只有当count变化时才会触发getNum函数的重新计算和渲染。如果不使用useMemo则任何一个state发生变化都会导致组件重新渲染进而导致getNum重新计算,耗费性能useMemo和useCallback的区别useMemo和useCallback接收的参数都是一样,第一个参数为回调,第二个参数为要依赖的数据共同作用:仅仅依赖数据发生变化,才会重新计算结果,也就是起到缓存的作用。两者区别:useMemo计算结果是计算函数返回来的值,主要用于缓存计算结果的值,应用场景如:需要计算的状态useCallback计算结果是计算函数,主要用于缓存函数,应用场景如:需要缓存的函数,因为函数式组件每次任何一个state的变化,整个组件都会被重新刷新,一些函数是没有必要重新刷新的,此时就应该缓存起来,提高性能,和减少资源浪费。注意:当父组件更新渲染(state和props变化)时,无论子组件的props是否改变都会默认更新渲染子组件。所以这种情况下,若你的useMemo或useCallback时用来传给子组件的props时,都必须借助React memo来包裹子组件完成缓存,才能避免子组件的无效多余更新自定义Hook自定义的Hook必须以use开头,这个约定非常重要,不遵循的话,由于无法判断某个函数是否包含对其内部Hook的调用,React将无法自动检查你的Hook是否违反了Hook的规则。useDidMountimport { useEffect } from 'react'; const useDidMount = fn => useEffect(() => fn && fn(), []); export default useDidMount;useWillUnmountuseEffect时已经提起过,其允许返回一个清除副作用的函数,当依赖项为[]时,其相当与componentWillUnMountimport { useEffect } from 'react'; const useWillUnmount = fn => useEffect(() => () => fn && fn(), []); export default useWillUnmount;实现类似class组件可支持回调的setState方法class组件更新状态时,setState可以通过第二个参数拿到更新完毕后的回调函数。很遗憾,虽然hooks函数的useState第二个参数回调支持类似class组件的setState第一个参数的用法(通过传入一个函数并将函数的返回值作为新的state进行更新),但不支持第二个参数回调,但是很多业务场景中我们又希望hooks组件能支持更新后的回调这一方法。借助useRef和useEffect配合useState来实现这一功能:const useXState = (initState) => { const [state, setState] = useState(initState) //表示有state值更新了 let isUpdate = useRef() const setXState = (state, cb) => { //这里setState是使用了函数参数的方式更新useState的值,而不是直接更新成指定的参数值 setState(prev => { isUpdate.current = cb return typeof state === 'function' ? state(prev) : state }) } useEffect(() => { if(isUpdate.current) { //存在更新state,则执行回调 isUpdate.current() } }) return [state, setXState] } export default useXState说明:利用useRef的特性来作为标识区分挂载还是更新,当执行setState时,会传入和setState一摸一样的参数,并且将回调赋值给useRef的current属性,这样在更新完成时,我们手动调用current即可实现更新后的回调这一功能Hooks vs Render Props vs HOC没有hooks之前,高阶组件和Render Props本质上都是将复用逻辑提升到父组件中。这样就能够避免HOC和Render Props带来的【嵌套地狱】。但是,像Context的和这样有父子层级关系(树状结构关系)的,还是只能使用Render Props或者HOC。对于Hooks、Render Props和告诫组件来说,它们都有各自的使用场景Hooks:替代Class的大部分用例,除了getSnapshotBeforeUpdate和componentDidCatch还不支持。可提取复用逻辑。除了有明确父子关系的,其他场景都可以使用Hooks。Render Props:在组件渲染上拥有更高的自由度,可以根据父组件提供的数据进行动态渲染。适合有明确父子关系的场景高阶组件:适合用来注入,并且生成一个新的可复用组件。适合用来写插件不过,能使用Hooks的场景还是应该优先使用Hooks,其次才是Render Props和HOC。当然,Hooks、Render Props和HOC不是对立的关系。我们既可以用Hook来写Render Props和HOC,也可以在HOC中使用Render Props和Hooks。参考文章:https://blog.csdn.net/qq_43293207/article/details/117631126
2024年12月06日
2 阅读
0 评论
1 点赞
1
2