React这些你都知道吗?
react基础
1、什么是React
- React 是 Facebook 在 2011 年开发的前端 JavaScript 库。
- 它遵循基于组件的方法,有助于构建可重用的UI组件。
- 它用于开发复杂和交互式的 Web 和移动 UI。
- 尽管它仅在 2015 年开源,但有一个很大的支持社区
2、React有什么特点?
React的主要功能如下:
- 它使用虚拟DOM 而不是真正的DOM。
- 它可以用服务器端渲染。
- 它遵循单向数据流或数据绑定。
3、列出React的一些主要优点
React的一些主要优点是:
- 它提高了应用的性能
- 可以方便地在客户端和服务器端使用
- 由于 JSX,代码的可读性很好
- React 很容易与 Meteor,Angular 等其他框架集成
- 使用React,编写UI测试用例变得非常容易
4、 React有哪些限制?
React的限制如下:
- React 只是一个库,而不是一个完整的框架
- 它的库非常庞大,需要时间来理解
- 新手程序员可能很难理解
- 编码变得复杂,因为它使用内联模板和 JSX【JSX 是 JavaScript XML 的简写】
5、类组件和函数组件之间有什么区别
类组件( Class components ) 无论是使用函数或是类来声明一个组件,它决不能修改它自己的 props 。 所有 React 组件都必须是纯函数,并禁止修改其自身 props 。
React是单项数据流,父组件改变了属性,那么子组件视图会更新。 属性 props 是外界传递过来的,状态 state 是组件本身的,状态可以在组件中任意修改 组件的属性和状态改变都会更新视图
函数组件(functional component) 函数组件接收一个单一的 props 对象并返回了一个React元素
函数组件和类组件当然是有区别的,而且函数组件的性能比类组件的性能要高,因为类组件使用的时候要实例化,而函数组件直接执行函数取返回结果即可。为了提高性能,尽量使用函数组件。
区别函数组件、类组件 【特别的hooks后面说】
- 是否有this
- 是否有生命周期
- 是否有状态state
6、React中的refs作用是什么?如何创建refs
Refs 是 React 提供给我们的安全访问 DOM 元素 或者 某个组件实例 的句柄
refs 并不是类组件的专属,函数式组件同样能够利用闭包暂存其值
Refs 是使用 React.createRef() 方法创建的
访问 this.myRef.current
函数组件添加refs:需要使用useRef和useImperativeHandle结合使用
useRef 返回的 ref 对象在组件的整个生命周期内保持不变,也就是说每次重新渲染函数组件时,返回的ref 对象都是同一个(使用 React.createRef ,每次重新渲染组件都会重新创建 ref)
forwardRef 转发
- 因为函数组件没有实例,所以函数组件无法像类组件一样可以接收 ref 属性
- forwardRef 可以在父组件中操作子组件的 ref 对象
- forwardRef 可以将父组件中的 ref 对象转发到子组件中的 dom 元素上
- 子组件接受 props 和 ref 作为参数
function Child(props,ref){
return (
<input type="text" ref={ref}/>
)
}
Child = React.forwardRef(Child);
function Parent(){
let [number,setNumber] = useState(0);
// 在使用类组件的时候,创建 ref 返回一个对象,该对象的 current 属性值为空
// 只有当它被赋给某个元素的 ref 属性时,才会有值
// 所以父组件(类组件)创建一个 ref 对象,然后传递给子组件(类组件),子组件内部有元素使用了
// 那么父组件就可以操作子组件中的某个元素
// 但是函数组件无法接收 ref 属性 <Child ref={xxx} /> 这样是不行的
// 所以就需要用到 forwardRef 进行转发
const inputRef = useRef();//{current:''}
function getFocus(){
inputRef.current.value = 'focus';
inputRef.current.focus();
}
return (
<>
<Child ref={inputRef}/>
<button onClick={()=>setNumber({number:number+1})}>+</button>
<button onClick={getFocus}>获得焦点</button>
</>
)
}
useImperativeHandle
- useImperativeHandle可以让你在使用 ref 时,自定义暴露给父组件的实例值
- 在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用
- 父组件可以使用操作子组件中的多个 ref
import React,{useState,useEffect,createRef,useRef,forwardRef,useImperativeHandle} from 'react';
function Child(props,parentRef){
// 子组件内部自己创建 ref
let focusRef = useRef();
let inputRef = useRef();
useImperativeHandle(parentRef,()=>(
// 这个函数会返回一个对象
// 该对象会作为父组件 current 属性的值
// 通过这种方式,父组件可以使用操作子组件中的多个 ref
return {
focusRef,
inputRef,
name:'计数器',
focus(){
focusRef.current.focus();
},
changeText(text){
inputRef.current.value = text;
}
}
});
return (
<>
<input ref={focusRef}/>
<input ref={inputRef}/>
</>
)
}
Child = forwardRef(Child);
function Parent(){
const parentRef = useRef();// {current:''}
function getFocus(){
parentRef.current.focus();
// 因为子组件中没有定义这个属性,实现了保护,所以这里的代码无效
parentRef.current.addNumber(666);
parentRef.current.changeText('<script>alert(1)</script>');
console.log(parentRef.current.name);
}
return (
<>
<ForwardChild ref={parentRef}/>
<button onClick={getFocus}>获得焦点</button>
</>
)
}
7、state 和 props有什么区别
state 和 props都是普通的JavaScript对象
props 是一个从外部传进组件的参数,主要作为就是从父组件向子组件传递数据,它具有可读性和不变性,只能通过外部组件主动传入新的 props 来重新渲染子组件,否则子组件的 props 以及展现形式不会改变。
state 的主要作用是用于组件保存、控制以及修改自己的状态,它只能在 constructor 中初始化,它算是组件的私有属性,不可通过外部访问和修改,只能通过组件内部的 this.setState 来修改,修改 state 属性会导致组件的重新渲染
8、**什么是高阶组件 ** 【案例:React-Redux connect 】
一个高阶组件只是一个包装了另外一个 React 组件的 React 组件
高阶组件本质上一个js函数,且该函数接受一个组件作为参数,并返回一个新的组件。基本上,这是从React的组成性质派生的一种模式,我们称它们为 “纯”组件, 因为它们可以接受任何动态提供的子组件,但它们不会修改或复制其输入组件的任何行为。
const EnhancedComponent = higherOrderComponent(WrappedComponent);
高阶组件作用
- 代码复用,逻辑抽象,抽离底层准备(bootstrap)代码
- 渲染劫持
- State 抽象和更改
- Props 更改
高阶组件两种形式
属性代理(Props Proxy):高阶组件操控传递给 WrappedComponent 的 props,
反向继承(Inheritance Inversion):高阶组件继承(extends)WrappedComponent
Props Proxy【属性代理】
- 更改 props
- 通过 refs 获取组件实例
- 抽象 state
- 把 WrappedComponent 与其它 elements 包装在一起
function CompHOC(WrappedComponent) {
return class Comp extends React.Component {
render() {
return <WrappedComponent {...this.props}/>
}
}
}
<WrappedComponent {...this.props}/>
// is equivalent to [相当于]
React.createElement(WrappedComponent, this.props, null)
Inheritance Inversion 【反向继承】
- 渲染劫持(Render Highjacking)
- 操作 state
function HOC(WrappedComponent) {
return class Enhancer extends WrappedComponent {
render() {
return super.render()
}
}
}
反向继承允许高阶组件通过 *this* 关键词获取 WrappedComponent,意味着它可以获取到 state,props,组件生命周期(component lifecycle)钩子,以及渲染方法(render)
渲染劫持 【*渲染 指的是 WrappedComponent.render 方法】
- 『读取、添加、修改、删除』任何一个将被渲染的 React Element 的 props
- 在渲染方法中读取或更改 React Elements tree,也就是 WrappedComponent 的 children
- 根据条件不同,选择性的渲染子树
- 给子树里的元素变更样式
HOC.displayName = `HOC(${getDisplayName(WrappedComponent)})`
//or
class HOC extends ... {
static displayName = `HOC(${getDisplayName(WrappedComponent)})`
...
}
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName ||
WrappedComponent.name ||
‘Component’
}
实际上你不用自己写这个方法,因为 recompose 库已经提供了
9、react组件
无状态组件和有状态组件
无状态组价和有状态组件**是否维护state值**来划分的
无状态组件: 是最基础的组件形式,纯静态展示的作用,如固定的按钮,标签等,它的基本组成结构就是属性(props),在有一个渲染函数(render),不涉及到状态的更新,所以无状态组件的复用性最强
**有状态组件:**在无状态组件的基础之上,如果内部包含state并且外部数据发生变化时会随之发生变化,此时就是有状态组件。有状态组件是有生命周期的,用来在不同的时刻触发状态(state)的更新
函数组件和类组件
函数组件和类组件的区分方式是**由定义的形式**来划分的。函数组件使用函数定义组件,类组件使用ES6 class定义组件
函数组件:函数组件的写法就是普通函数的写法。函数组件比类组件更加简洁; 而且函数组件看似这是返回一个DOM结构,其实函数组件用的是无状态组件的思想。在函数组件中你无法使用state也不能用生命周期,这就决定了函数组件是展示型组件,
【函数组件】更容易理解,它的功能只是接收属性,渲染页面,不执行与UI无关的其他逻辑,而且函数组件中没有this,无需考虑this带来的问题
【类组件】的写法是用的es6的class方法。类组件的功能比函数组件强大,类组件可以维护自身的state状态,它有不同的生命周期方法,可以让开发者在组建不同的生命周期阶段对组件做出更多的控制(挂载,更新,卸载)
纯组件
纯(Pure) 组件是可以编写的最简单、最快的组件。它们可以替换任何只有 render() 的组件。这些组件增强了代码的简单性和应用的性能
无论是 memo 还是 pureComponent,都是会对过去的 props,state,和现在的 props state 进行一次浅比较。这两个对象的长度是否相等,每个 key 是否两个对象都有,是不是都是一个引用,至于说,这个引用是不是也是相同的,浅比较不会去做,比方说,你的 props 更新了,但是更新的这个 value 是一个引用,那么浅比较依然会认为这两个 props 是相同的,因此不会去更新
PureComponent,ShouldComponentUpdate,还有 memo 都是为了减少子组件不必要的渲染而存在的,达到提升 react 性能的目的
纯函数 [案例:Redux reducer ]
一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数 函数的返回结果只依赖于它的参数 函数执行过程里面没有副作用
let add = (a, b)=>a+b
add(1,2) // 3
高阶函数
高阶函数是对其他函数进行操作的函数,操作可以是将它们作为参数,或者是返回它们。 简单来说,高阶函数是一个接收函数作为参数或将函数作为输出返回的函数
案例:【Array.prototype.map,Array.prototype.filter 和 Array.prototype.reduce 是语言中内置的一些高阶函数】
function add(x,y,z) {
return z(x) + z(y);
}
add(-3, -4, Math.abs); // ==> Math.abs(-3) + Math.abs(-4) ==> 7
Math.abs(x) 函数返回指定数字 “x“ 的绝对值
受控组件和非受控组件
非受控组件,即组件的状态不受React控制的组件 【即不受setState()
的控制】
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Demo extends Component {
render() {
return (
<input />
)
}
}
ReactDOM.render(<Demo/>, document.getElementById('app'))
受控组件,受控组件就是组件的状态受React控制
class Demo extends Component {
constructor(props) {
super(props);
this.state = {
value: props.value
}
}
handleChange(e) {
this.setState({
value: e.target.value
})
}
render() {
return (
<input value={this.state.value} onChange={e => this.handleChange(e)}/>
)
}
}
高阶组件(HOC)
HOC本质上一个js函数,且该函数接受一个组件作为参数,并返回一个新组件,HOC并不是一个 React API
。 它只是一种设计模式,类似于装饰器模式。
react生命周期(15/16/17版本)
10、setState 什么时候同步什么时候异步
- setState 只在合成事件(react为了解决跨平台,兼容性问题,自己封装了一套事件机制,代理了原生的事件,像在jsx中常见的onClick、onChange这些都是合成事件)和钩子函数(生命周期)中是“异步”的,在原生事件和 setTimeout 中都是同步的
- setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果
- setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新
react生命周期
react 15 生命周期
React15使用的是栈调和器,栈调和器是递归执行调和操作的,更新一旦开始,中途就无法中断。倘若递归层级过深,则可能会造成浏览器渲染卡顿
1、挂载过程 constructor() componentWillMount() componentDidMount()
2、更新过程 componentWillReceiveProps(nextProps) shouldComponentUpdate(nextProps,nextState) componentWillUpdate (nextProps,nextState) render() componentDidUpdate(prevProps,prevState)
3、卸载过程 componentWillUnmount()
其具体作用分别为: 1、constructor() 完成了React数据的初始化。
2、componentWillMount() 组件已经完成初始化数据,但是还未渲染DOM时执行的逻辑,主要用于服务端渲染。
3、componentDidMount() 组件第一次渲染完成时执行的逻辑,此时DOM节点已经生成了。
4、componentWillReceiveProps(nextProps) 接收父组件新的props时,重新渲染组件执行的逻辑。
5、shouldComponentUpdate(nextProps, nextState) 在setState以后,state发生变化,组件会进入重新渲染的流程时执行的逻辑。在这个生命周期中return false可以阻止组件的更新,主要用于性能优化。
6、componentWillUpdate(nextProps, nextState) shouldComponentUpdate返回true以后,组件进入重新渲染的流程时执行的逻辑。
7、render() 页面渲染执行的逻辑,render函数把jsx编译为函数并生成虚拟dom,然后通过其diff算法比较更新前后的新旧DOM树,并渲染更改后的节点。
8、componentDidUpdate(prevProps, prevState) 重新渲染后执行的逻辑。
9、componentWillUnmount() 组件的卸载前执行的逻辑,比如进行“清除组件中所有的setTimeout、setInterval等计时器”或“移除所有组件中的监听器removeEventListener”等操作。
react 16 17生命周期
React16使用的是全新的Fiber调和器,这就依托于 React16的重点了一Fiber架构,目前简要概括就是,React16能够 实现中断调和,分批次异步地调和。从而达到不因为JS执行时间过久影响渲染卡顿
react 17 生命周期 与16差不多 【React16.8 常用Hook新特性】
具体参考博文:网址
React17 1、支持混用 2、更改事件委托:原来的 react 的事件系统是挂在 document 上的,为了支持混用将会修改到你挂载的 rootNode 中 3、useEffect 的回调修改为异步调用:useEffect 的副作用清理函数是在 effect 执行之后立马执行的,但是在使用中发现了如果回调中的操作比较耗时,会造成一些性能问题,现在 useEffect 的 副作用清理函数会在 render 后执行了。 4、返回一致的 undefined 错误 class 和 function 最好使用 return null 来代替
react 18 新特性 新api
- 自动批处理以减少渲染【批处理是 React将多个状态更新分组到单个重新渲染中以获得更好的性能】
- Suspense 的 SSR 支持
- startTransition
- ReactDOM.createRoot【ReactDOM.createRoot(root).render(jsx)】
import { startTransition } from 'react' ;
// 显示输入的
setInputValue ( input ) ;
// 将内部的任何状态更新标记为转换 省去节流 输入框搜索调用接口
startTransition ( ( ) => {
// Transition: 显示结果
setSearchQuery ( input ) ;
} ) ;
react hooks 【react版本v16.8后】
1、为什么 React 要加入Hooks
- 组件之间的逻辑状态难以复用
- 大型复杂的组件很难拆分
- Class语法的使用不友好比如:
类组件可以访问生命周期,函数组件不能
类组件可以定义维护state(状态),函数组件不可以
类组件中可以获取到实例化后的this,并基于这个this做一些操作,而函数组件不可以
mixins:变量作用于来源不清、属性重名、Mixins引入过多会导致顺序冲突
HOC和Render props:组件嵌套过多,不易渲染调试、会劫持props,会有漏洞
有了hooks
Hooks 就是让你不必写class组件就可以用state和其他的React特性;
也可以编写自己的hooks【自定义hooks】在不同的组件之间复用
优点:
没有破坏性改动:完全可选的。 你无需重写任何已有代码就可以在一些组件中尝试 Hook。100% 向后兼容的。 Hook 不包含任何破坏性改动。
更容易复用代码:它通过自定义hooks来复用状态,从而解决了类组件逻辑难以复用的问题
函数式编程风格:函数式组件、状态保存在运行环境、每个功能都包裹在函数中,整体风格更清爽、优雅
代码量少,复用性高
更容易拆分
Hooks缺点
- hooks 是 React 16.8 的新增特性、16.8前版本的不可用
- 状态不同步(闭包带来的坑):函数的运行是独立的,每个函数都有一份独立的闭包作用域。当我们处理复杂逻辑的时候,经常会碰到“引用不是最新”的问题
- 使用useState时候,使用push,pop,splice等直接更改数组对象的坑,demo中使用push直接更改数组无法获取到新值,应该采用析构方式原因:push,pop,splice是直接修改原数组,react会认为state并没有发生变化,无法更新)
- useState 初始化只初始化一次
- useEffect 内部不能修改 state
- useEffect 依赖引用类型会出现死循环
- 不要在循环,条件或嵌套函数中调用Hook,必须始终在React函数的顶层使用Hook。这是因为React需要利用调用顺序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易导致调用顺序的不一致性,从而产生难以预料到的后果
2、怎么避免react hooks的常见问题
安装ESLint 插件 约束 [Hook 规则]
npm install eslint-plugin-react-hooks --save-dev
// 你的 ESLint 配置
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error", // 检查 Hook 的规则
"react-hooks/exhaustive-deps": "warn" // 检查 effect 的依赖
}
}
- 不要在
useEffect
里面写太多的依赖项,划分这些依赖项成多个单一功能的useEffect
。其实这点是遵循了软件设计的“单一职责模式” - 如果你碰到状态不同步的问题,可以考虑下手动传递参数到函数
hooks闭包的坑有哪些?如何解决 【很多时候你不得不使用useRef方案】
问题:每次 render 都有一份新的状态,数据卡在闭包里,捕获了每次 render 后的 state,也就导致了输出原来的 state
解决:可以通过 useRef 来保存 state。前文讲过 ref 在组件中只存在一份,无论何时使用它的引用都不会产生变化,因此可以来解决闭包引发的问题。
- 复杂业务的时候,使用Component代替hooks
3、hooks模拟class生命周期场景
constructor(){
super()
this.state={count:0}
}
// Hooks模拟constructor
const [count setCount]=useState(0)
componentDidMount(){
console.log('componentDidMount')
}
// Hooks模拟 componentDidMount
useEffect(()=>console.log('componentDidMount'),[])
// useEffect拥有两个参数,第一个参数作为回调函数会在浏览器布局和绘制完成后调用,因此它不会阻碍浏览器的渲染进程,第二个参数是一个数组,也是依赖项
// 当依赖列表存在并有值,如果列表中的任何值发生更改,则每次渲染后都会触发回调
// 当它不存在时,每次渲染后都会触发回调
// 当它是一个空列表时,回调只会被触发一次,类似于componentDidMount
shouldComponentUpdate(nextProps,nextState){
console.log('shouldComponentUpdate')
return true //更新组件 反之不更新
}
// React.memo包裹一个组件来对它的props进行浅比较,但这不是一个hooks,因为它的写法和hooks不同,其实React.memo等效于PureComponent,但它只比较props
// 模拟 shouldComponentUpdate
const MyComponent = React.memo(
whateverComponent,
(prevProps,nextProps) => nextProps.count !== preProps.count
)
componentDidMount() {console.log('componentDidMount');}
componentDidUpate(){console.log('componentDidUpate')}
// Hooks模拟 componentDidUpdate
useEffect(()=>console.log('componentDidMount or componentDidUpate'))
// 这里的回调函数会在每次渲染后调用,因此不仅可以访问componentDidUpdate,还可以访问componentDidMount,如果只想模拟componentDidUpdate,我们可以这样来实现
const mounted = useRef()
useEffect(() => {
if(!mounted.current){
mounted.current = true
} else {
console.log('componentDidUpate')
}
})
// useRef在组件中创建“实例变量”,它作为一个标志来指示组件是否处于挂载或更新阶段。当组件更新完成后在会执行else里面的内容,以此来单独模拟componentDidUpdate
componentWillUnmount(){
console.log('componentWillUnmount')
}
// Hooks模拟 componentWillUnmount
useEffect(()=>{
// 此处并不同于willUnMount porps发生变化即更新,也会执行结束监听
// 准确的说:返回的函数会在下一次effect执行之前,被执行
return ()=>{console.log('componentWillUnmount')}
},[])
// 当在useEffect的回调函数中返回一个函数时,这个函数会在组件卸载前被调用。我们可以在这里清除定时器或事件监听器
总结:
- 默认的useEffect(不带[])中return的清理函数,它和componentWillUnmount有本质区别的,默认情况下return,在每次useEffect执行前都会执行,并不是只有组件卸载的时候执行
- useEffect在副作用结束之后,会延迟一段时间执行,并非同步执行,和compontDidMount有本质区别。遇到dom操作,最好使用useLayoutEffect。
- hooks 模拟的生命周期与class中的生命周期不尽相同,我们在使用时,还是需要思考业务场景下那种方式最适合
4、Hooks性能优化
- useMemo 缓存数据
- useCallback 缓存函数
- 相当于class组件的SCU【shouldComponentUpdate】和PureComponent
5、hooks基础语法基础使用,常用hooks API
useState
function Button() {
const [buttonText, setButtonText] = useState("点击前");
const handleClick = () => {
setButtonText("点击后");
};
return <button onClick={handleClick}>{buttonText}</button>;
}
useEffect
import React, { useState, useEffect } from "react";
const SetIntervalHooksComp = (props) => {
const [count, setCount] = useState(0);
useEffect(() => {
let timer = setInterval(() => {
setCount(count + 1);
}, 1000);
// 当在 useEffect 的回调函数中返回一个函数时,这个函数会在组件卸载前被调用
return () => clearInterval(timer);
// count发生变化时再次执行
}, [count]);
return <div>{count}</div>;
};
export default SetIntervalHooksComp;
useRef
import React, { useRef, useEffect } from "react";
function UseRefHooksComp() {
// 初始化一个useRef
const content = useRef(null);
useEffect(() => {
// 通过.current获取
console.log(content.current);
}, []);
return <div ref={content}>useRef用法</div>;
}
export default UseRefHooksComp;
useContext
import React, { useContext } from "react";
const UseContextHooksComp = () => {
const AppContext = React.createContext({});
const A = () => {
const { name } = useContext(AppContext);
return <p>我是A的子组件--{name}</p>;
};
const B = () => {
const { name } = useContext(AppContext);
return <p>我是B组件的名字--{name}</p>;
};
return (
<AppContext.Provider value={{ name: "useContext-hook测试" }}>
<A />
<B />
</AppContext.Provider>
);
};
export default UseContextHooksComp;
useReducer
import React, { useReducer } from "react";
const UseReducerComp = () => {
const reducer = (state, action) => {
console.log(state, "action", action);
if (action.type === "add") {
return {
...state,
count: state.count + 1,
};
} else {
return state;
}
};
const addcount = () => {
dispatch({
type: "add",
});
};
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<>
<p>{state.count}</p>
<button onClick={addcount}>count++</button>
</>
);
};
export default UseReducerComp;
useMemo
import React, { Component, useState, useMemo } from "react";
class UseMemoComp extends Component {
render() {
return (
<div>
<p>useMemoComp test</p>
<WithNoMemo />
<WithMemo />
</div>
);
}
}
// 不使用 useMemo
function WithNoMemo() {
const [count, setCount] = useState(1);
const [val, setValue] = useState("");
function expensive() {
console.log("compute");
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
}
return (
<div>
<h4>
{count}-{val}-{expensive()}
</h4>
<div>
<button onClick={() => setCount(count + 1)}>+c1</button>
<input value={val} onChange={(event) => setValue(event.target.value)} />
</div>
</div>
);
}
// 使用useMemo来执行昂贵的计算,然后将计算值返回,并且将count作为依赖值传递进去。这样,就只会在count改变的时候触发expensive执行,在修改val的时候,返回上一次缓存的值
function WithMemo() {
const [count, setCount] = useState(1);
const [val, setValue] = useState("");
const expensive = useMemo(() => {
console.log("compute");
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
}, [count]);
return (
<div>
<h4>
{count}-{val}-{expensive}
</h4>
{val}
<div>
<button onClick={() => setCount(count + 1)}>+c1</button>
<input value={val} onChange={(event) => setValue(event.target.value)} />
</div>
</div>
);
}
export default UseMemoComp;
useCallback
基础用法
import React, { useState, useCallback } from "react";
const set = new Set();
// 每次修改count,set.size就会 +1,这说明useCallback依赖变量count,count变更时会返回新的函数;而val变更时,set.size不会变,说明返回的是缓存的旧版本函数
export default function UseCallbackComp() {
const [count, setCount] = useState(1);
const [val, setVal] = useState("");
const callback = useCallback(() => {
console.log(count, "count"); // 不会打印
}, [count]);
set.add(callback);
return (
<div>
<h4>{count}</h4>
<h4>{set.size}</h4>
<div>
<button onClick={() => setCount(count + 1)}>add</button>
<input value={val} onChange={(event) => setVal(event.target.value)} />
</div>
</div>
);
}
场景:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新
import React, { useState, useCallback, useEffect } from 'react';
function Parent() {
const [count, setCount] = useState(1);
const [val, setVal] = useState('');
const callback = useCallback(() => {
return count;
}, [count]);
return <div>
<h4>{count}</h4>
<Child callback={callback}/>
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<input value={val} onChange={event => setVal(event.target.value)}/>
</div>
</div>;
}
function Child({ callback }) {
const [count, setCount] = useState(() => callback());
useEffect(() => {
setCount(callback());
}, [callback]);
return <div>
{count}
</div>
}
useImperativeHandle
useImperativeHandle 应当与 forwardRef 一起使用
import React, {
Component,
useRef,
useImperativeHandle,
forwardRef,
} from "react";
// forwardRef 引用传递(Ref forwading)是一种通过组件向子组件自动传递 引用ref 的技术
const MyFunctionComponent = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
}));
return <input ref={inputRef} />;
});
class UseImperativeHandleComp extends Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
componentDidMount() {
this.textInput.current.focus();
}
render() {
return <MyFunctionComponent ref={this.textInput} />;
}
}
export default UseImperativeHandleComp;
useLayoutEffect
import React, { useState, useLayoutEffect, useEffect } from "react";
export default function UseLayoutEffectComp() {
const [count, setCount] = useState(0);
const onClick = () => {
setCount((i) => i + 1);
};
useEffect(() => {
console.log("useEffect"); // 后打印
});
useLayoutEffect(() => {
// 改成 useEffect 试试
console.log("useLayoutEffect"); // 先打印
});
return (
<div>
<h1>count: {count}</h1>
<button onClick={onClick}>Click</button>
</div>
);
}
// 总结:
// useEffect在浏览器渲染完成后执行
// useLayoutEffect在浏览器渲染前【在DOM更新后执行】执行
// useLayoutEffect总是比useEffect先执行
// useLayoutEffect里面的任务最好影响了Layout(布局)
// 注意:为了用户体验最好优先使用useEffect
6、自定义hooks 和 自定义hooks的用法
- 通过自定义 Hook,可以将组件逻辑提取到可重用的函数中
- 自定义 Hook 是一个函数,其名称必须以 “
use
” 开头,函数内部可以调用其他的 Hook - 自定义 Hook 是一种自然遵循 Hook 设计的约定,而并不是 React 的特性
- 两个组件中使用相同的 Hook 不会共享 state
- 在一个组件中多次调用
useState
和useEffect
,它们是完全独立的,每次调用 Hook,它都会获取独立的 state - 自定义 Hook 解决了以前在 React 组件中无法灵活共享逻辑的问题
编写一个 useReducer
的 Hook,使用 reducer 的方式来管理组件的内部 state
function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
function dispatch(action) {
const nextState = reducer(state, action);
setState(nextState);
}
return [state, dispatch];
}
// 官网 demo 使用 自定义 useReducer
function Todos() {
const [todos, dispatch] = useReducer(todosReducer, []);
function handleAddClick(text) {
dispatch({ type: 'add', text });
}
// ...bla bla...
}
编写 和 使用 自定义hook
import React, { useState, useEffect } from 'react'
const useMousePosition = () => {
const [position, setPosition] = useState({x: 0, y: 0 })
useEffect(() => {
const updateMouse = (e) => {
setPosition({ x: e.clientX, y: e.clientY })
}
document.addEventListener('mousemove', updateMouse)
return () => {
document.removeEventListener('mousemove', updateMouse)
}
})
return position
}
export default useMousePosition
// 在需要的地方引入然后调用即可
import React from 'react'
import useMousePosition from './useMousePosition'
function App() {
const position = useMousePosition()
return (
<div>
<div>x: {position.x}</div>
<div>y: {position.y}</div>
</div>
)
}
常用自定义hooks举例
function userDebounce(fn,delay,deps=[]){
let {current} = useRef({fn, timer: null})
setEffect(() => {
current.fn = fn;
},[fn]);
return userCallback(function(...args){
if(current.timer) clearTimeout(crurent.timer);
current.timer = setTimeout(() => {
current.fn.call(this,...args);
},delay);
},deps);
}
function useThrottle(fn,interval){
let {current} = useRef({fn,timer:null});
setEffect(() => {
current.fn = fn;
},[fn])
return useCallback(function(...args){
if(!current.timer){
current.timer = setTimeout(() => {
delete current.tiemer;
},interval);
current.fn.call(this,...args);
}
},deps);
}
// eleRef 是用 useRef 获得的 React元素引用
function useScrollPos(eleRef){
const [pos,setPos] = useState([0,0]);
useEffect(() => {
function onScroll)(){
setPos([eleRef.current.scrollLeft,eleRef.current.scrollTop]);
}
eleRef.current.addEventListener('scroll', onScroll);
return () => {
eleRef.current.removeEvenlistener('scroll',onScroll);
}
},[]);
return pos;
}
function useTitle(title){
setEffect(() => {
document.tilte = title;
},[])
}
function useWinSize() {
const [size, setSize] = useState({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
})
const onResize = useCallback(() => {
setSize({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
})
}, [])
useEffect(() => {
window.addEventListener('resize', onResize)
return () => {
window.removeEventListener('reisze', onResize)
}
}, [])
return size
}
// 相比 setInterval, useInterval 就一个用处, delay 可以动态修改
function useInterval(callback, delay) {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
});
useEffect(() => {
function tick() {
savedCallback.current();
}
let timer = setInterval(tick, delay);
return () => clearInterval(timer);
}, [delay]);
}
7、hooks useMemo 和 useCallback的区别
useMemo:
useMemo是在render期间执行的。所以不能进行一些额外的副操作,比如网络请求等
传递一个创建函数和依赖项,创建函数会需要返回一个值,只有在依赖项发生改变的时候,才会重新调用此函数,返回一个新的值
用来缓存数据,当组件内部某一个渲染的数据,需要通过计算而来,这个计算是依赖与特定的state、props数据,我们就用useMemo来缓存这个数据,以至于我们在修改她们没有依赖的数据源的情况下,多次调用这个计算函数,浪费计算资源。
useCallback
useCallback返回一个函数,只有在依赖项变化的时候才会更新(返回一个新的函数)
缓存一个函数,这个函数如果是由父组件传递给子组件,或者自定义hooks里面的函数【通常自定义hooks里面的函数,不会依赖于引用它的组件里面的数据】,这时候我们可以考虑缓存这个函数,好处:
- 不用每次重新声明新的函数,避免释放内存、分配内存的计算资源浪费
- 子组件不会因为这个函数的变动重新渲染。【和React.memo搭配使用】
等价 useMemo表示useCallback
useCallback(fn,[m]);
等价于
useMemo(() => fn, [m]);
结论
useCallback缓存的是函数,useMemo 缓存的是函数的返回结果。
useCallback 是来优化子组件的,防止子组件的重复渲染。
useMemo 可以优化当前组件也可以优化子组件,优化当前组件主要是通过 memoize 来将一些复杂的计算逻辑进行缓存。当然如果只是进行一些简单的计算也没必要使用 useMemo
版权声明:
作者:wuhou123
链接:https://wuhou.fun/103.html
来源:前端网
文章版权归作者所有,未经允许请勿转载。

共有 0 条评论