react日常学习记录

154 阅读14分钟

一、React.memo VS useMemo

React.memo 是对整个函数组件包裹。
第二个参数如果不传,默认是浅比较;
如果传,则需要自己传入一个函数,参数为(prevProps, nextProps),手动进行比较。

useMemo可以对组件进行细粒度化进行优化,可以只包裹某一部分,

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

useMemo() 返回的是一个 memoized 值,只有当依赖项
(比如上面的 a,b 发生变化的时候,才会重新计算这个 memoized 值)

memoized 值不变的情况下,不会重新触发渲染逻辑.

二、useMemo Vs useCallback

两者的参数相同的,第一个是函数,第二个是依赖项;
useMemo将调用函数并返回结果;
useCallback是将函数返回而不是结果。

三、React组件之间传参数

1. 父 --- > 子: props

2. 子 --- > 父: 回调函数

3. 父 --- > 孙: context.Provider   context.consumer

4. 使用redux,一般在react项目中使用react-redux

5. 使用hox   主要api:
                    // 可以使用react中各个hook APi,一个项目中可以创建多个
                    createModel(() => {
                        return {}
                    })

6. 还可以使用自定义hooks,可以实现。

四、useCallback的隐患启示录

当useCallback和useEffect组合使用时,由于useCallback的依赖项变化也会导致useEffect执行,
这种隐式依赖会带来BUG或隐患。

鉴于这种情况的出现,所以建议将函数挂到Ref上面,ref引用不论怎么样都保持不变,
而且函数每次render ref上又会绑定最新的函数,不会有闭包问题。

为了方便起见,不用每一次都单独写ref,所以写一个通用的方法

exports fuction useRefCallback<T extends (...args: any[]) => any>(callback: T)) {
    const callbackRef = useRef(callback);
    callbackRef.current = callback;
    return useCallback((...args): any[]) => callbackRef.current(...args), []) as T;
}

五、useReducer

useReducer 接受两个参数(reducer,initState)


// 第一个参数:应用的初始化
const initialState = {count: 0};

// 第二个参数,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>
        </>
    );
}
常用于state是一个数组或者对象的时候。比如说登录时,需要用户名/密码/验证码时的表单数据。

六、setState是同步的还是异步的

1. 在合成事件里是异步的;
2. 在react的钩子函数中是异步的;
3. 在原生js事件中是同步的;
4. 在setTimeout事件中是同步的。

七、react的事件处理

1. react事件和dom事件的区别

    · react事件使用小驼峰命名法,而不是全小写;
    · react事件在阻止默认事件时,不能使用return false,而是要显示写明preventDefault();
    · 在jsx中要传入一个函数作为事件处理函数,而不是字符串。
    · 在react中,一般不需要用addEventListener为已创建的dom元素添加监听器,
        而只需要在该元素初始渲染的时候添加即可
    
2. react类组件中this的问题。
    
    在react的class中,不会默认绑定this,如果你忘记绑定,那么this是undefined
    
    · 在构造器中使用bind进行绑定 【推荐】
        
            constructor(props) {
                super(props);
                 // 为了在回调中使用 `this`,这个绑定是必不可少的    
                 this.handleClick = this.handleClick.bind(this);  
            }
        
    · 使用箭头函数去定义函数 [ 实验性语法 ]
    
       
          // 注意: 这是 *实验性* 语法。  
          handleClick = () => {    console.log('this is:', this);  }

          render() {
            return (
              <button onClick={this.handleClick}>
                Click me
              </button>
            );
          }

    · 在回调中使用箭头函数 【推荐】
    
        
        class LoggingButton extends React.Component {
          handleClick() {
            console.log('this is:', this);
          }
          render() {
            // 此语法确保 `handleClick` 内的 `this` 已被绑定。    
            return (      
                <button onClick={() => this.handleClick()}>        
                    Click me
                </button>
            );
          }
        }

3. 向事件处理函数传递参数
    
    <button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
    <button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

八、react的合成事件

   合成事件SyntheticEvent,是浏览器原生事件的跨浏览器包装器,能够兼容所有的浏览器。
   
   react的大部分事件都是在冒泡阶段被触发,如果想要在捕获阶段触发,则应为事件名加上Capture。
   
   例如: onClick事件如果想在捕获阶段触发,应该说onClickCapture。
   
   注:
   
  ** 
   · react17开始,合成事件不再放入事件池;
   
   · 从 React 17 开始,`onScroll` 事件在 React 中**不再冒泡**。
   这与浏览器的行为一致,并且避免了当一个嵌套且可滚动的元素在其父元素触发事件时造成混乱。
   
   **
   
       

九、react事件和原生事件的执行顺序

    function App() {

      function reactPranetClick() {
        console.log(' react事件: parent- 冒泡');
      }
      function reactPranetClickcaptrue() {
        console.log('react事件: parent- 捕获');
      }

      function reactChildClick() {
        console.log(' react事件: child- 冒泡');
      }
      function reactChildClickcaptrue() {
        console.log('react事件: child- 捕获');
      }

      useEffect(() => {
        document.addEventListener("click", (e) => {
          console.log("原生事件:document DOM 事件捕获---");
        }, true);
        document.addEventListener("click", (e) => {
          console.log("原生事件:document DOM 事件冒泡!");
        }, false);
        document.getElementById('btn')?.addEventListener('click', () => {
          console.log('原生事件:child - 捕获')
        }, true)
        document.getElementById('btn')?.addEventListener('click', () => {
          console.log('原生事件:child - 冒泡')
        }, false)
        document.getElementById('box')?.addEventListener('click', () => {
          console.log('原生事件:parent - 捕获')
        }, true)
        document.getElementById('box')?.addEventListener('click', () => {
          console.log('原生事件:parent - 冒泡')
        }, false)
      }, [])
      return (
        <div className="App">
          <div
            id='box'
            onClick={() => { reactPranetClick() }}
            onClickCapture={() => { reactPranetClickcaptrue() }}>
            <button id='btn'
              onClick={() => { reactChildClick() }}
              onClickCapture={() => { reactChildClickcaptrue() }}
            >点击</button>
          </div>
        </div>
      );
    }
    
    // 结果:
    
    原生事件:document DOM 事件捕获---
    react事件: parent- 捕获
    react事件: child- 捕获
    原生事件:parent - 捕获
    原生事件:child - 捕获
    原生事件:child - 冒泡
    原生事件:parent - 冒泡
    react事件: child- 冒泡
    react事件: parent- 冒泡
    原生事件:document DOM 事件冒泡!

十、react事件委托的变更

从react v17 版本开始,react事件的委托放在了根DOM节点上,而不是document上。这是为了实现渐进式升级。

十一、react生命周期

    1. 组件挂在并插入dom时
        constructor
        getDrivedStateFromProps
        render
        componentDidMount
    2. 组件更新阶段
        getDrivedStateFromProps
        shouldComponentUpdate
        render
        getSnapShotBeforeUpdate
        componentDidUpdate
     3. 组件卸载
         componentWillUnMount
     4. 错误处理
         getDrivedStateFromError
         componentDidCatch
   ### 
       getSnapShotBeforeUpdate 在最近一次渲染输出(提交到DOM节点)之前调用。
       使用:
           滚动到聊天位置。

十二、hooks的使用规则

       · 只在最顶层使用,不要在条件、循环、嵌套函数中使用。
           // 因为在react的Fiber中,hooks都是以链表的形式存在的,如果在条件、循环、嵌套函数中使用
           // 会打乱hooks出现的位置。
       · 只在react函数中使用。不要在普通js函数中使用。
       

十三、 react中Fiber是什么?

      可以理解为,react内部实现的一套状态更新机制。
      支持不同任务的优先级,可中断,可恢复,并且中断后恢复,可复用之前的中间状态.
      
      Fiber的工作原理是:双缓存Fiber树。
     
      currentFiber.alternate === workInProgressFiber;
      workInProgressFiber.alternate === currentFiber;
 

十四、调用setState后,发生了什么

   1. 在setSate之后,react 会为当前节点创建一个updateQueue的更新队列;
   2. 然后触发reconciliation过程,在这个过程中,会使用名为Fiber的调度算法,开始生成新的Fiber树,Fiber的最大特点就是异步可中断的执行;
   3. React Scheduler会根据优先级高低,先执行优先级高的节点,具体执行doWork方法;
   4. 在doWork中,React会执行一边updateQueue中的方法,以获得新的节点,然后对比新旧节点,为老节点打上更新、插入、替换等tag;
   5. 在当前节点doWork完成后,会执行performUnitOfWork方法获得新节点,然后再重复上面的过程;
   6. 当所有节点都doWork完成后,会触发commitRoot方法,React进入commit阶段;
   7. 在commit阶段,React会根据之前各个节点打的tag,一次性的更新整个Dom元素。

十五、为什么React不同步执行this.state

    1. 这样会破坏props和state的一致性,造成一些难以debug的问题。
    2. 这样会让一些我们正在实现的新功能变得无法实现。
    

十六、什么是虚拟DOM,为什么虚拟DOM会提高性能

1. 虚拟DOM,就是用js对象去表示真实DOM节点。

2. 虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom的 diff 算法避免了没有必要的dom操作,从而提高性能。

十七、react hooks的优缺点

    ·优点(解决了哪些问题):
        1. 在组件之间复用状态逻辑很难。
        2. 复杂组件变得难以理解
        3. 难以理解的class
    ·缺点
        1. 响应式的useEffect
        2. 状态不同步

十八、hooks会不会覆盖class的所有使用场景

    目前对于getSnapShotBeforeUpdate, getDrivedStateFromError , componentDidCatch 还没有等价写法。

十九、hooks会替代render props 或高阶组件?

    这两种模式仍有用武之地,比如:一个虚拟滚动条组件或许有一个renderItem组件,或者是一个可见的容器组件或许有他自己的DOM结构。但是大部分情况已经可以覆盖。
    

二十、react中jsx的理解

    它是一个js的语法扩展,在react中使用jsx,可以很好的描述UI应该呈现出它应有交互的本质形式。
    

二十一、React中如本区分class组件还是Function组件

    所有的class类组件都继承于React.Component,所以借助于判断原型链上是否有React.Component即可
    AComponent.prototype instanceof React.Component

二十二、 useEffect 清楚副作用的方法

      就是return一个解绑函数。
      

二十三、render Props

    render Props 是指一种在React组件之间使用值为函数的prop共享代码的简单技术
     
    <DataProvider render={data => (
      <h1>Hello {data.target}</h1>
    )}/>

二十四、useState 和 setState的不同

    1. useState是只能在函数组件中使用的,setState是在class组件中使用的。
    
    2. useState不会自动合并对象。可以使用函数式setState结合扩展运算符
            setState(pre => {
                return {...pre, ...updateValue}
            })
    3. useState可以声明多个,而在类组件中,一般都是在this.state中。
    
    4. 一般情况下,state改变时:
        
        · useState修改state时,同个useState声明的值会被覆盖处理,多个useState的值会触发多次渲染
        · setState修改state时,多个setState会被合并处理
    5. useState修改state时,设置相同的值,函数组件不回重新渲染。
        而类组件即使是相同的值也会重新渲染。
        
    
            

二十五、React.createClass / extends React.component 区别

    1. 语法
        React.createClass本质上是一个工厂函数
        extends React.component 更加接近最新的es6 的class规范。
    2. propType 和 getDefaultProps
        React.createClass 使用propType**对象**和getDefaultProps()*方法*来获取和设置props
        extends React.Component使用两个属性propType 和 defaultProps
    3. 状态的区别
        React.createClass使用getInitialState()方法返回一个包含初始值的状态
        Extends React.component使用设置constructor属性
    4. this的问题
        前者会正确绑定this
        后者由于使用es6语法,导致this不能正确绑定
    5. mixins
        前者在创建组件的时候可以穿入mixins属性
        后者将没有这个属性
            

二十六、react可以在render访问refs吗?

    不可以。
    此时真实的DOM还没有渲染,无法获取DOM。
    可以在pre-commit 或者 commit阶段获取。

二十七、react组件的构造函数是必须的吗

    不是必须的
    对于函数式组件,内部没有需要自己维护的state,只需要接受props即可
            

二十八、React.forwardRef

    React.forwardRef会创建一个React组件,这个组件能够将其接受到的Ref转发到组件树的下一级组件。
    这种技术不常见,但是在:高阶组件和转发RefsDOM组件,这两种形式下很有作用。
    
    
    const FancyButton = React.forwardRef((props, ref) => (  
        <button ref={ref} className="FancyButton">    
            {props.children}
        </button>
    ));

    // You can now get a ref directly to the DOM button:
    const ref = React.createRef();
    <FancyButton ref={ref}>Click me!</FancyButton>;
            
            

二十九、react中的错误边界

     错误边界是一种 React 组件,
     这种组件**可以捕获发生在其子组件树任何位置的 JavaScript 错误,并打印这些错误,
     同时展示降级 UI**,而并不会渲染那些发生崩溃的子组件树。
     错误边界可以捕获发生在整个子组件树的渲染期间、生命周期方法以及构造函数中的错误。
     

三十、Profiler

     测量一个 React 应用多久渲染一次以及渲染一次的“代价”。 
     它的目的是识别出应用中渲染较慢的部分,或是可以使用[类似 memoization 优化]的部分,并从相关优化中获益。

三十一、为什么React元素会有一个$$typeof

     是为了防止XSS攻击,因为$$typeof的值是一个Symbol, symbol无法被序列化。
    

三十二、为什么要用getSnapshotBeforeUpdate来代替componentWillUpdate

        在 React 开启异步渲染模式后,
        在 render 阶段读取到的 DOM 元素状态并不总是和 commit 阶段相同,
        这就导致在componentDidUpdate 中使用 componentWillUpdate 中读取到的 DOM 元素状态是不安全的,
        因为这时的值很有可能已经失效了。

        getSnapshotBeforeUpdate 会在最终的 render 之前被调用,
        也就是说在 getSnapshotBeforeUpdate 中读取到的 DOM 元素状态是可以保证与 componentDidUpdate 中一致的。
        此生命周期返回的任何值都将作为参数传递给componentDidUpdate()。
            

三十三、严格模式下,setState两次调用及解决办法

      1. 原因:
          严格模式下不能自动检测到你的副作用,但它可以帮助你发现他们,使其更具稳定性。
          这是通过有意的进行双调用来完成的:
              · class component constructor
              · render
              · setState
              · static getDrivedStateFromProps
              
          注:只在开发模式下生效,生产模式不生效
          
       2. 解决
           · 去掉严格模式 (最常用最简单的方法)
               root.render(
                  // <React.StrictMode>
                    <App />
                  // </React.StrictMode>
                );
            
            · 使用深拷贝,将setState拷贝一份。
            

三十四、react-router的原理分析

    1. react-router中涉及到的重要的api,是history,Router,Route,Link。
    
    2. history,是有browserHistory,hashHitory,memoryHistory这三种,其中前两种是常用的。
        
        browserHitory,是采用push和replace两种方式来实现url的改变。
        这两种分别封装了history对象的pushState和replaceState两种方法
        
        History中同样可以改变url的方法有go,back,forward,这些方法都会触发popState,
        所以在这里browserHistory采用手动触发popState的方式来触发url的改变的。
        
        hashHistory是通过区分history对象的location属性中包含的hash字段来展示不同的组件的。
        通过手动触发hashChange事件
        
    3. Router,根据当前的url来渲染Component,这里它调用了history监听url变化的listen函数。    
        
    4. Route,用于声明路由映射到应用程序组件层。它会根据props.match来决定渲染的component。    
        
    5. Link,允许用户浏览应用的主要方式。
            
            相比a标签,它的点击事件会默认添加阻止默认事件的行为。
            

三十五、react常见的hooks

         1. useState
         2. useReducer
         3. useRef
         4. useEffect
         5. useLayoutEffect
         6. useCallback
         7. useMemo
         8. useContext
         9. useImperativeHandle
             可以让你在使用ref时自定义暴露给父组件的实例值。一般搭配forwardRef使用
         10. useId
             生成唯一的id标识
         11. useTransition
             const [isPending, startTransition] = useTransition();
             返回一个状态值表示过渡任务的等待状态,以及一个启动该过渡任务的函数。
         12. useDebugValue
             用于在react开发者工具中,显示自定义hook的标签。
         

三十六、为何react事件要自己绑定this

    在 React 的类组件中,当我们把事件处理函数引用作为回调传递过去,如下所示
    <button type="button" onClick={this.handleClick}>Click Me</button>
    事件处理程序方法会丢失其隐式绑定的上下文。
    当事件被触发并且处理程序被调用时,this的值会回退到默认绑定,即值为 undefined,
    这是因为类声明和原型方法是以严格模式运行。
    当我们将事件处理程序的 this 绑定到构造函数中的组件实例时,我们可以将它作为回调传递,而不用担心会丢失它的上下文
  

三十七、为什么建议传递给setState的函数的参数是回调函数而不是对象

    因为this.props和this.state可能是异步的,不能依赖他们的值去计算下一个state。
    

三十八、redux遵循的三个原则是

    1. 单一数据源
    2. 状态是只读的
    3. 使用纯函数进行修改
    

三十九、单页面应用的SEO

    1. 使用服务端渲染
    
    2. 适用于渲染(prerender-spa-plugin)
    
        生成一张张的静态html文件
        
    3. 使用两套代码,一套代码是给爬虫使用的,无需样式,一套是给用户展示用的。缺点就是要维护两套代码。但是对于已经开发完的单页面应用很有效,不需要重新开发。    
        
        

四十、reducer为什么必须是纯函数

    1. 纯函数:
        · 相同的输入永远返回相同的输出
        · 不修改函数的输入值
        · 不依赖外部生态环境
        · 无任何副作用
        
    2. 解析:
        因为在redux里面,比较两个新旧对象,比较的是两个对象的储存位置,也就是浅比较。
        所以当我们直接返回旧的state时,redux认为没有变化,所以页面也不回更新。
        
    3. 为什么redux要这么设计    
        
        因为比较两个js对象中所有属性是否相同,唯一的办法就是深比较。
        然而深比较在真实的代码中是非常大的,非常耗性能的。需要比较的次数非常多。所以一个有效的规定就是:
        当无论发生任何变化时,开发者都要返回一个新的state。
        没有变化时,就返回旧的state的。
        

四十一、