React和响应式系统 | 青训营笔记

56 阅读5分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天

鼠鼠直接贴之前面试记的笔记

谈谈对React的理解

React是一个网页UI库,通过组件化的方式解决视图层开发复用的问题,本质是一个组件化框架

核心设计思想

声明式 :优势在于直观与组合

组件化 :优势在于视图的拆分与模块复用,容易做到 低耦合高内聚

通用性 : 优势在于一次学习随处编写,比如React Natvie ,React 360 ,主要靠虚拟Dom来保证实现,使得React的使用范围变得足够广,无论是Web,Native ,VR,甚至是Shell应用都可以进行开发

劣势

没有提供一揽子解决方案,在开发大型项目中需要在社区寻找解决方案,虽然一定程度上 促进了社区的发展 ,但也为开发者 在技术选型和学习适用上造成了一定成本

为什么React要使用JSX

一句话解释JSX

JSX是一个Javascript的语法拓展,也可以说是一个类似于XML的ECMAscript的语法拓展

它本身没有太多的语法定义,也不希望引入更多的标准

核心概念

  • React本身并不强制使用JSX
  • JSX本身是React.createElement的语法糖
  • React需要将组件转化为虚拟DOM数
  • XML在树结构的描述上天生具有可读性强的优势
  • JSX会使代码变得简洁清晰
  • babel将Jsx会还原成React.createElement

方案对比

模板 :引入了过多新的概念和语法

模板字符串 :代码结构变得复杂代码提示困难

JXSON : 没有很好的语法提示

Babel如何将JSX转化为JS的

module.exports = function(babel){
    var t = babel.type
    return{
        name:"custom-jsx-plugin",
        visitor:{
        JSXElement(path){
            var openingElement = path.node.openingElement
            var tagName = openingElement.name.name
            var args = []
            args.push(t.stringLiteral(tagName))
            var attribs = t.nullLiteral()
            args.push(attribs)
            var reactIdentifer = t.identifer("React")
            var createElementIdentifer = t.identifer("createElement")
            var callee = t.memberExpression(reactIdentifer,createElementIdentifer)
            var callExpression = t.callExpression(callee,args)
            callExpression.arguments = 				callExpression.arguments.concat(path.node.children)
			path.replaceWith(callExpression,path.node)	            
        }	
    }
    }
}

React组件生命周期

概念

挂载阶段

  1. constructor :常用于初始化 (社区不推荐这么写) 直接顶层 初始化state

  2. getDerviedStateFromProps : 在props变化时候更新state

  3. UNSAFE_componentWillMount : 用于组件将要加载前做一些操作,但已经被标记为弃用,因为在React的异步渲染机制下,该方法可能会被多次调用

  4. render :返回JSX结构,用于描述具体的渲染内容

  5. componentDidMount:用于组件加载完成时候做某些事情

更新阶段

  1. UNSAFE_componentWillReceiveProps

  2. getDerviedStateFromProps

  3. shouldComponentUpdate : 进行浅比较判断是否进行更新

  4. UNSAFE_componentWillUpdate

  5. render

  6. getSnapshotBeforeUpdate :返回值作为componentDidUpdate第三个参数使用

  7. componentDidUpdate :更新完成

卸载阶段

componentWillUnmount:用于清理工作,比如说定时器

其他

  • 函数组件没有生命周期,任何时候都会进行重新渲染,但官方提供React.memo来跳过渲染
  • PureComponent默认实现了shouldComponentUpdate,仅在props和state进行浅比较后,确认变更后才会触发重新渲染
  • 错误边界:getDerivedStateFromError,componentDidCatch捕获错误,渲染时的报错只能通过componentDidCatch捕获

进阶提问

React的请求应该放在哪里

  • 对于异步请求,应该放在componentDidMount里面
  • 从时间顺序来看,除componentDidMount还可以有constructor和componentWillMount,一个很少用,一个已经被标记为废弃并且易引发bug,不利于代码维护
  • 最好放在componentDidmount

类组件和函数组件的区别

共同点

实际用途一样,无论是 高阶组件 还是 异步加载组件,都可以作为 基础展示UI

不同点

基础认知

  • 本质代表了两种不同的设计思想和心智模式

  • 类组件的根基是OOP,面向对象的编程

  • 函数组件的根基是FP,也就是函数式编程

  • 相较于类组件,函数组件更纯粹,简单,易测试

使用场景

  • 在不使用Recompose或者hooks的情况下,如需使用生命周期,就用类组件,限定场景是固定的
  • 在hooks的加持下,类组件和函数组件的边界就变得模糊了
  • 在需要继承的情况下用类组件,但是目前来说组合优于继承,所以,这方面优势也在淡出

性能优化

  • 类组件通过 shouldComponentUpdate 函数来阻断渲染
  • 函数组件通过React.memo来优化

未来趋势

  • 函数组件成为未来的主推方案
  • 因为类组件this的模糊性,业务逻辑散落在生命周期中,代码缺乏标准的拆分模式
  • hooks提供比原先更细腻的逻辑拆分和复用,适合时间切片和并发模式

如何设计React组件

设计分类

展示组件

概念

  • 只做展示,不额外增加功能的组件
  • 受制于外部的props
  • 具有极强的通用性复用性

分类

  • 代理组件:用于封装常用属性减少代码重复

  • 样式组件:把样式内聚在自己的内部

  • 布局组件:把布局内聚在自己的内部

灵巧组件

概念

  • 处理业务逻辑和数据状态
  • 面向业务,功能更丰富,复杂性更高,复用度更低

分类

  • 容器组件:把网络请求逻辑处理放在容器组件中处理

  • 高阶组件:逻辑复用的高阶手段

    使用场景

    • 检查登录态

    • 封装埋点

    • 可以基于装饰器进行链式调用

    • 渲染元素的劫持,比如loading状态的展示

    缺陷

    • 无法获取静态方法,可以通过复制调用,社区也有解决方案
    • refs无法透传,但可以通过React.forwardRefs来解决

进阶

如何在渲染劫持中为原本的渲染结果添加新的样式?

const withLoading = (WrappedComponent)=>{
    return class extends WrappedComponent{
        render(){
            if(this.props.isLoading){
                return <Loading/>
            }else{
				return super.render()
            }
        }
    }
}