关于react.js:一道React面试题把我整懵了

发问:react我的项目中的JSX里,onChange={this.func.bind(this)}的写法,为什么要比非bind的func = () => {}的写法效率高?

申明: 因为自己程度无限,有考虑不周之处,或者呈现谬误的,请严格指出,小弟感激不尽。这是小弟第一篇文章,有啥潜规则不懂的,你们就通知我。小弟今天有分享,等分享完了之后,持续欠缺。

之前不经意间看到这道题,据说是阿里p5-p6级别的题目,咱们先看一下这道题目,明面上是考查对react的理解深度,实际上波及的考点很多:bind,arrow function,react各种绑定this的办法,优缺点,适宜的场景,类的继承,原型链等等,所以综合性很强。

咱们明天的主题就是由此题目,来总结一下相干的知识点,这里我会着重剖析题目中第二种绑定计划

五种this绑定计划的差异性

计划一: React.createClass

这是老版本React中用来申明组件的形式,在那个版本,没有引入class这种概念,所以通过这种形式来创立一个组件类(constructor)
ES6的class相比createClass,移除了两点:一个是mixin 一个是this的主动绑定。前者能够用HOC代替,后者则是完完全全的没有,起因是FB认为这样能够防止和JS的语法产生混同,所以去掉了。
应用这种办法,咱们不须要放心this,它会主动绑定到组件实例身上,然而这个API曾经废除了,所以只须要理解。

const App = React.createClass({
  handleClick() {
    console.log(this)
  },
  render() {
    return <div onClick={this.handleClick}>你好</div>
  }
})

计划二:在render函数中应用bind

class Test extends Component {
  handleClick() {
    console.log(this)
  }
  render() {
    return <div onClick={this.handleClick.bind(this)}></div>
  }
}

计划三:在render函数中应用箭头函数

class Test extends Component {

  handleClick() {
    console.log(this)
  }
  render() {
    return <div onClick={() => this.handleClick()}></div>
  }
}

这两个计划简洁明了,能够传参,然而也存在潜在的性能问题: 会引起不必要的渲染

咱们经常会在代码中看到这些场景: 更多演示案例请点击

class Test extends Component {
  render() {
    return <div>
      <Input />
      <button>增加<button>
      <List options={this.state.options || Immutable.Map()} data={this.state.data} onSelect={this.onSelect.bind(this)} /> // 1 pureComponent
    </div>
  }
}

场景一:应用空对象/数组来做兜底计划,防止options没有数据时运行时报错。
场景二:应用箭头函数来绑定this。

可能在一些不须要关怀性能的场景下这两种写法没有什么太大的害处,然而如果咱们正在思考性能优化,譬如咱们应用了PureComponent来去优化咱们的渲染性能
这外面React有应用shallowEqual做第一层的比拟,这个时候咱们关注的可能是这个data(数据是否有变动从而影响渲染),然而被咱们漠视的options,onSelect却会间接导致PureComponent生效,然而咱们找不到优化失败的起因。

而假如咱们的外围data是Immutable的,这样其实优化了咱们做diff相干的性能。当data为null时,此时咱们冀望的是不会反复渲染,然而当咱们的Test组件有状态更新,触发了Test的从新渲染,此时render执行,List依旧会从新渲染。起因就是咱们每次执行render,传递给子组件的options,onSelect是一个新的对象/函数。这样在做shallowEqual时,会认为有更新,所以会更新List组件。

这个中央也有很多解决方案:

  1. 不要间接在render函数外面做兜底,或者应用同一援用的数据源
  2. 对于事件监听函数,咱们能够当时做好绑定,应用计划4或者5,或者最新的hook(useCallback、useMemo)
const onSelect = useCallback(() => {
  ... //和select相干的逻辑
}, []) // 第二个参数是相干的依赖,只有依赖变了,onSelect才会变,设置为空数组,示意永远不变

计划四:在构造函数中应用bind

class Test extends Component {
  constrcutor() {
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    console.log(this)
  }

  render() {
    return <Button onClick={this.handleClick}>测试</Button>
  }
}

这种计划是React举荐的形式,只在实例化组件的时候做一次绑定,之后传递的都是同一援用,没有计划二、三带来的负面效应。

然而这种写法绝对2,3繁琐了许多:

1. 如果咱们并不需要在构造函数里做什么的话,为了做函数绑定,咱们须要手动申明构造函数; 这里没有思考到实例属性的新写法,间接在顶层赋值。感激@Yes好2012斧正。

  1. 针对一些简单的组件(要绑定的办法过多),咱们须要多次重复的去写这些办法名;
  2. 无奈独自解决传参问题(这一点尤其重要,也限度了它的应用场景)。

计划五:应用箭头函数定义方法(class properties)

这种技术依赖于Class Properties提案,目前还在stage-2阶段,如果须要应用这种计划,咱们须要装置@babel/plugin-proposal-class-properties

class Test extends Component {
  handleClick = () => {
    console.log(this)
  }

  render() {
    return <button onClick={this.handleClick}>测试</button>
  }
}

这也是咱们面试题中提到的第二种绑定计划
先总结一下长处:

  1. 主动绑定
  2. 没有计划二、三所带来的渲染性能问题(只绑定一次,没有生成新的函数);
  3. 能够再封装一下,应用params => () => {}这种写法来达到传参的目标。

咱们在babel上做一下编译:点击class-properties(抉择ES2016或者更高,须要手动装置一下这个pluginbabel-plugin-transform-class-properties相比于@babel/plugin-proposal-class-properties更直观,前者是babel6命名形式,后者是babel7)

在应用plugin编译后的版本咱们能够看到,这种计划其实就是间接在构造函数中定义了一个change属性,而后赋值为箭头函数,从而实现的对this的绑定,看起来很完满,很精妙。然而,正是因为这种写法,意味着由这个组件类实例化的所有组件实例都会调配一块内存来去存储这个箭头函数。而咱们定义的一般办法,其实是定义在原型对象上的,被所有实例共享,就义的代价则是须要咱们应用bind手动绑定,生成了一个新的函数。

咱们看一下bind函数的polyfill:

if (!Function.prototype.bind) {
    ... // do sth
    var fBound  = function() {
          // this instanceof fBound === true时,阐明返回的fBound被当做new的结构函数调用
          return fToBind.apply(this instanceof fBound
                 ? this
                 : oThis,
                 // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };
    ... // do sth

    return fBound;
  };
}

如果在不反对bind的浏览器上,其实编译后,也就相当于新生成的函数的函数体就一条语句: fToBind.apply(...)

咱们以图片的模式看一下差距:

注: 图中,虚线框面积代表援用函数所节俭的内存,实线框的面积代表耗费的内存。
图一:应用箭头函数做this绑定。只有render函数定义在原型对象上,由所有实例对象共享。其余内存耗费都是基于每个实例上的。
图二:在构造函数中做this绑定。render,handler都定义在原型对象上,实例上的handler实线框代表应用bind生成的函数所耗费的内存大小。

如果咱们的handler函数体自身就很小,实例数量不多,绑定的办法不多。两种计划在内存占用上的差异性不大,然而一旦咱们要在handler里解决简单的逻辑,或者该组件可能会产生大量的实例,抑或是该组件有大量的须要绑定办法,第一种的劣势就突显进去了。

如果说下面这种绑定this的计划只用在React上,可能咱们只须要思考下面几点,然而如果咱们应用下面的办法去创立一些工具类,可能留神的不止这些。

说到类,可能大家都会想到类的继承,如果咱们须要重写某个基类的办法,运行上面,你会发现,和设想中的相差甚远。

class Base {
  sayHello() {
    console.log('Hello')
  }

  sayHey = () => {
    console.log('Hey')
  }
}

class A extends Base {
  constructor() {
    super()
    this.name = 'Bitch'
  }

  sayHey() {
    console.log('Hey', this.name)
  }
}

new A().sayHello()  // 'Hello'
new A().sayHey() // 'Hey'

注: 咱们心愿打印出 ‘Hello’ ‘Hey Bitch’,理论打印的是:’Hello’ ‘Hey’

起因很简略,在A的构造函数内,咱们调用super执行了Base的构造函数,向A实例上增加属性,这个时候执行Base构造函数后,A实例上曾经有了sayHey属性,它的值是一个箭头函数,打印出·Hey·
而咱们重写的sayHey其实是定义在原型对象上的。所以最终执行的是在Base里定义的sayHey办法,但不是同一个办法。
据此,咱们还能够推理一下假如咱们要先执行Base的sayHey,而后在此基础上执减少逻辑咱们又该怎么做?上面这种计划必定是行不通的。

sayHey() {
  super.sayHey() // 报错
  console.log('get off!')
}

多说一句: 有大佬认为这种办法的性能并不好,它考查的点是ops/s(每秒能够实例化多少个组件,越多越好),最终得出的论断是

然而就有人提出质疑,这些办法咱们最终都会通过babel编译成浏览器能辨认的代码,那么最终运行的版本所体现的差异性是否可能代表其实在的差异性。具体的我也没细看,有须要理解更多的,能够
看一下这篇文章Arrow Functions in Class Properties Might Not Be As Great As We Think

据此,咱们曾经cover了这道题少数考点,如果下次碰到这种题,或者想出这类题无妨从上面的角度去思考下

  1. 面试者的角度:
    1.1 在答复这道题之前,写解释两种计划的原理,显然,面试官想要着重考查的是第二种的理解状况,他背地到底做了什么。而后谈谈他们一些惯例的优缺点
    1.2 答复对于效率的问题,前者每次bind,都会生成一个新的函数,然而函数体内代码量少,最重要的还是援用的原型上的handler,这个是共享的。然而前面这一种,他会在每个实例上生成一个函数,如果实例数量多,或者函数体大,或者是绑定函数过多,那么占用的内存就显著要超出第一种。
  2. 面试官的角度: 考bind实现,考react的绑定策略,优缺点,考性能优化策略,考箭头函数, 考原型链,考继承。发散开来,真的很广。

总结:

每种绑定计划既然存在就有其存在的理由(除了第一种曾经是过来),然而也会有相应的弊病,并没有相对的谁好谁差,咱们在应用时,能够依据理论场景做抉择。
这道题目答到点不难,怎么让面试官感觉你懂得全面还是挺难的。

其次针对this绑定计划, **如果特地在意性能,就义一点代码量,可读性:举荐四其次,如果本人自身够仔细,二三也能够应用,然而肯定要留神新生成的函数是否会导致多余渲染;
如果想不加班:举荐五(如何传参文章中有提及)。**

  • 减少shouldComponentUpdate钩子对新旧props进行比拟,如果值雷同则阻止更新,防止不必要的渲染,或者应用PureReactComponent代替Component,其外部曾经封装了shouldComponentUpdate的浅比拟逻辑
  • 对于列表或其余构造雷同的节点,为其中的每一项减少惟一key属性,以不便Reactdiff算法中对该节点的复用,缩小节点的创立和删除操作
  • render函数中缩小相似onClick={() => {doSomething()}}的写法,每次调用render函数时均会创立一个新的函数,即便内容没有产生任何变动,也会导致节点没必要的重渲染,倡议将函数保留在组件的成员对象中,这样只会创立一次
  • 组件的props如果须要通过一系列运算后能力拿到最终后果,则能够思考应用reselect库对后果进行缓存,如果props值未发生变化,则后果间接从缓存中拿,防止昂扬的运算代价
  • webpack-bundle-analyzer剖析以后页面的依赖包,是否存在不合理性,如果存在,找到优化点并进行优化

前端react面试题具体解答

在调用setState 之后产生了什么

  • 状态合并,触发和谐:

    setState函数之后,会将传入的参数对象与以后的状态合并,而后登程调用过程

  • 依据新的状态构建虚构dom树

    通过和谐过程,react会高效的依据新的状态构建虚构DOM树,筹备渲染整个UI页面

  • 计算新老树节点差别,最小化渲染

    得倒新的虚构DOM树后,会计算出新老树的节点差别,会依据差别对界面进行最小化渲染

  • 按需更新

    在差别话计算中,react能够绝对精确的晓得哪些地位产生了扭转以及该如何扭转,这保障按需更新,而不是发表从新渲染

应用 React 有何长处

  • 只需查看 render 函数就会很容易晓得一个组件是如何被渲染的
  • JSX 的引入,使得组件的代码更加可读,也更容易看懂组件的布局,或者组件之间是如何相互援用的
  • 反对服务端渲染,这能够改良 SEO 和性能
  • 易于测试
  • React 只关注 View 层,所以能够和其它任何框架(如Backbone.js, Angular.js)一起应用

HOC相比 mixins 有什么长处?

HOC 和 Vue 中的 mixins 作用是统一的,并且在晚期 React 也是应用 mixins 的形式。然而在应用 class 的形式创立组件当前,mixins 的形式就不能应用了,并且其实 mixins 也是存在一些问题的,比方:

  • 隐含了一些依赖,比方我在组件中写了某个 state 并且在 mixin 中应用了,就这存在了一个依赖关系。万一下次他人要移除它,就得去 mixin 中查找依赖
  • 多个 mixin 中可能存在雷同命名的函数,同时代码组件中也不能呈现雷同命名的函数,否则就是重写了,其实我始终感觉命名真的是一件麻烦事。。
  • 雪球效应,尽管我一个组件还是应用着同一个 mixin,然而一个 mixin 会被多个组件应用,可能会存在需要使得 mixin 批改本来的函数或者新增更多的函数,这样可能就会产生一个保护老本

HOC 解决了这些问题,并且它们达成的成果也是统一的,同时也更加的政治正确(毕竟更加函数式了)。

为什么应用jsx的组件中没有看到应用react却须要引入react?

实质上来说JSX是React.createElement(component, props, ...children)办法的语法糖。在React 17之前,如果应用了JSX,其实就是在应用React, babel 会把组件转换为 CreateElement 模式。在React 17之后,就不再须要引入,因为 babel 曾经能够帮咱们主动引入react。

hooks父子传值

父传子
在父组件中用useState申明数据
 const [ data, setData ] = useState(false)

把数据传递给子组件
<Child data={data} />

子组件接管
export default function (props) {
    const { data } = props
    console.log(data)
}
子传父
子传父能够通过事件办法传值,和父传子有点相似。
在父组件中用useState申明数据
 const [ data, setData ] = useState(false)

把更新数据的函数传递给子组件
<Child setData={setData} />

子组件中触发函数更新数据,就会间接传递给父组件
export default function (props) {
    const { setData } = props
    setData(true)
}
如果存在多个层级的数据传递,也可按照此办法顺次传递

// 多层级用useContext
const User = () => {
 // 间接获取,不必回调
 const { user, setUser } = useContext(UserContext);
 return <Avatar user={user} setUser={setUser} />;
};

componentWillReceiveProps调用机会

  • 曾经被废除掉
  • 当props扭转的时候才调用,子组件第二次接管到props的时候

为什么 React 要用 JSX?

JSX 是一个 JavaScript 的语法扩大,或者说是一个相似于 XML 的 ECMAScript 语法扩大。它自身没有太多的语法定义,也不冀望引入更多的规范。

其实 React 自身并不强制应用 JSX。在没有 JSX 的时候,React 实现一个组件依赖于应用 React.createElement 函数。代码如下:

class Hello extends React.Component {
  render() {
    return React.createElement(
        'div',
        null, 
        `Hello ${this.props.toWhat}`
      );
  }
}
ReactDOM.render(
  React.createElement(Hello, {toWhat: 'World'}, null),
  document.getElementById('root')
);

而 JSX 更像是一种语法糖,通过相似 XML 的形容形式,刻画函数对象。在采纳 JSX 之后,这段代码会这样写:

class Hello extends React.Component {
  render() {
    return <div>Hello {this.props.toWhat}</div>;
  }
}
ReactDOM.render(
  <Hello toWhat="World" />,
  document.getElementById('root')
);

通过比照,能够清晰地发现,代码变得更为简洁,而且代码构造档次更为清晰。

因为 React 须要将组件转化为虚构 DOM 树,所以在编写代码时,实际上是在手写一棵构造树。而XML 在树结构的形容上天生具备可读性强的劣势。

但这样可读性强的代码仅仅是给写程序的同学看的,实际上在运行的时候,会应用 Babel 插件将 JSX 语法的代码还原为 React.createElement 的代码。

总结: JSX 是一个 JavaScript 的语法扩大,构造相似 XML。JSX 次要用于申明 React 元素,但 React 中并不强制应用 JSX。即便应用了 JSX,也会在构建过程中,通过 Babel 插件编译为 React.createElement。所以 JSX 更像是 React.createElement 的一种语法糖。

React 团队并不想引入 JavaScript 自身以外的开发体系。而是心愿通过正当的关注点拆散放弃组件开发的纯正性。

fetch封装

npm install whatwg-fetch --save  // 适配其余浏览器
npm install es6-promise

export const handleResponse = (response) => {
  if (response.status === 403 || response.status === 401) {
    const oauthurl = response.headers.get('locationUrl');
    if (!_.isEmpty(oauthUrl)) {
      window.location.href = oauthurl;
      return;
    }
  }
  if (!response.ok) {
    return getErrorMessage(response).then(errorMessage => apiError(response.status, errorMessage));
  }
  if (isJson(response)) {
    return response.json();
  }
  if (isText(response)) {
    return response.text();
  }

  return response.blob();
};

const httpRequest = {
  request: ({
    method, headers, body, path, query,
  }) => {
    const options = {};
    let url = path;
    if (method) {
      options.method = method;
    }
    if (headers) {
      options.headers = {...options.headers,...headers};
    }
    if (body) {
      options.body = body;
    }
    if (query) {
      const params = Object.keys(query)
        .map(k => `${k}=${query[k]}`)
        .join('&');
      url = url.concat(`?${params}`);
    }
    return fetch(url, Object.assign({}, options, { credentials: 'same-origin' })).then(handleResponse);
  },
};

export default httpRequest;

React Hooks在平时开发中须要留神的问题和起因

(1)不要在循环,条件或嵌套函数中调用Hook,必须始终在 React函数的顶层应用Hook

这是因为React须要利用调用程序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易导致调用程序的不一致性,从而产生难以预料到的结果。

(2)应用useState时候,应用push,pop,splice等间接更改数组对象的坑

应用push间接更改数组无奈获取到新值,应该采纳析构形式,然而在class外面不会有这个问题。代码示例:

function Indicatorfilter() {
  let [num,setNums] = useState([0,1,2,3])
  const test = () => {
    // 这里坑是间接采纳push去更新num
    // setNums(num)是无奈更新num的
    // 必须应用num = [...num ,1]
    num.push(1)
    // num = [...num ,1]
    setNums(num)
  }
return (
    <div className='filter'>
      <div onClick={test}>测试</div>
        <div>
          {num.map((item,index) => (              <div key={index}>{item}</div>
          ))}      </div>
    </div>
  )
}

class Indicatorfilter extends React.Component<any,any>{
  constructor(props:any){
      super(props)
      this.state = {
          nums:[1,2,3]
      }
      this.test = this.test.bind(this)
  }

  test(){
      // class采纳同样的形式是没有问题的
      this.state.nums.push(1)
      this.setState({
          nums: this.state.nums
      })
  }

  render(){
      let {nums} = this.state
      return(
          <div>
              <div onClick={this.test}>测试</div>
                  <div>
                      {nums.map((item:any,index:number) => (                          <div key={index}>{item}</div>
                      ))}                  </div>
          </div>

      )
  }
}

(3)useState设置状态的时候,只有第一次失效,前期须要更新状态,必须通过useEffect

TableDeail是一个公共组件,在调用它的父组件外面,咱们通过set扭转columns的值,认为传递给TableDeail 的 columns是最新的值,所以tabColumn每次也是最新的值,然而理论tabColumn是最开始的值,不会随着columns的更新而更新:

const TableDeail = ({    columns,}:TableData) => {
    const [tabColumn, setTabColumn] = useState(columns) 
}

// 正确的做法是通过useEffect扭转这个值
const TableDeail = ({    columns,}:TableData) => {
    const [tabColumn, setTabColumn] = useState(columns) 
    useEffect(() =>{setTabColumn(columns)},[columns])
}

(4)善用useCallback

父组件传递给子组件事件句柄时,如果咱们没有任何参数变动可能会选用useMemo。然而每一次父组件渲染子组件即便没变动也会跟着渲染一次。

(5)不要滥用useContext

能够应用基于 useContext 封装的状态管理工具。

在哪个生命周期中你会收回Ajax申请?为什么?

Ajax申请应该写在组件创立期的第五个阶段,即 componentDidMount生命周期办法中。起因如下。
在创立期的其余阶段,组件尚未渲染实现。而在存在期的5个阶段,又不能确保生命周期办法肯定会执行(如通过 shouldComponentUpdate办法优化更新等)。在销毀期,组件行将被销毁,申请数据变得无意义。因而在这些阶段发岀Ajax申请显然不是最好的抉择。
在组件尚未挂载之前,Ajax申请将无奈执行结束,如果此时发出请求,将意味着在组件挂载之前更新状态(如执行 setState),这通常是不起作用的。
在 componentDidMount办法中,执行Ajax即可保障组件曾经挂载,并且可能失常更新组件。

指出(组件)生命周期办法的不同

  • componentWillMount — 多用于根组件中的应用程序配置
  • componentDidMount — 在这能够实现所有没有 DOM 就不能做的所有配置,并开始获取所有你须要的数据;如果须要设置事件监听,也能够在这实现
  • componentWillReceiveProps — 这个周期函数作用于特定的 prop 扭转导致的 state 转换
  • shouldComponentUpdate — 如果你放心组件适度渲染,shouldComponentUpdate 是一个改善性能的中央,因为如果组件接管了新的 prop, 它能够阻止(组件)从新渲染。shouldComponentUpdate 应该返回一个布尔值来决定组件是否要从新渲染
  • componentWillUpdate — 很少应用。它能够用于代替组件的 componentWillReceivePropsshouldComponentUpdate(但不能拜访之前的 props)
  • componentDidUpdate — 罕用于更新 DOM,响应 prop 或 state 的扭转
  • componentWillUnmount — 在这你能够勾销网络申请,或者移除所有与组件相干的事件监听器

高阶组件

高阶函数:如果一个函数承受一个或多个函数作为参数或者返回一个函数就可称之为高阶函数

高阶组件:如果一个函数 承受一个或多个组件作为参数并且返回一个组件 就可称之为 高阶组件

react 中的高阶组件

React 中的高阶组件次要有两种模式:属性代理反向继承

属性代理 Proxy

  • 操作 props
  • 抽离 state
  • 通过 ref 拜访到组件实例
  • 用其余元素包裹传入的组件 WrappedComponent

反向继承

会发现其属性代理和反向继承的实现有些相似的中央,都是返回一个继承了某个父类的子类,只不过属性代理中继承的是 React.Component,反向继承中继承的是传入的组件 WrappedComponent

反向继承能够用来做什么:

1.操作 state

高阶组件中能够读取、编辑和删除WrappedComponent组件实例中的state。甚至能够减少更多的state项,然而十分不倡议这么做因为这可能会导致state难以保护及治理。

function withLogging(WrappedComponent) {    
    return class extends WrappedComponent {    
        render() {    
            return (    
                <div>;    
                    <h2>;Debugger Component Logging...<h2>;    
                    <p>;state:<p>;    
                    <pre>;{JSON.stringify(this.state, null, 4)}<pre>;    
                    <p>props:<p>;    
                    <pre>{JSON.stringify(this.props, null, 4)}<pre>;    
                    {super.render()}    
                <div>;    
            );    
        }    
    };    
}

2.渲染劫持(Render Highjacking)

条件渲染通过 props.isLoading 这个条件来判断渲染哪个组件。

批改由 render() 输入的 React 元素树

react 最新版本解决了什么问题,减少了哪些货色

React 16.x的三大新个性 Time Slicing、Suspense、 hooks

  • Time Slicing(解决CPU速度问题)使得在执行工作的期间能够随时暂停,跑去干别的事件,这个个性使得react能在性能极其差的机器跑时,依然放弃有良好的性能
  • Suspense (解决网络IO问题) 和lazy配合,实现异步加载组件。 能暂停以后组件的渲染, 当实现某件事当前再持续渲染,解决从react出世到当初都存在的「异步副作用」的问题,而且解决得非的优雅,应用的是 T异步然而同步的写法,这是最好的解决异步问题的形式
  • 提供了一个内置函数componentDidCatch,当有谬误产生时,能够敌对地展现 fallback 组件; 能够捕捉到它的子元素(包含嵌套子元素)抛出的异样; 能够复用谬误组件。

(1)React16.8 退出hooks,让React函数式组件更加灵便,hooks之前,React存在很多问题:

  • 在组件间复用状态逻辑很难
  • 简单组件变得难以了解,高阶组件和函数组件的嵌套过深。
  • class组件的this指向问题
  • 难以记忆的生命周期

hooks很好的解决了上述问题,hooks提供了很多办法

  • useState 返回有状态值,以及更新这个状态值的函数
  • useEffect 承受蕴含命令式,可能有副作用代码的函数。
  • useContext 承受上下文对象(从 React.createContext返回的值)并返回以后上下文值,
  • useReducer useState 的代替计划。承受类型为 (state,action)=> newState的reducer,并返回与dispatch办法配对的以后状态。
  • useCalLback 返回一个回顾的memoized版本,该版本仅在其中一个输出产生更改时才会更改。纯函数的输入输出确定性 o useMemo 纯的一个记忆函数 o useRef 返回一个可变的ref对象,其Current 属性被初始化为传递的参数,返回的 ref 对象在组件的整个生命周期内放弃不变。
  • useImperativeMethods 自定义应用ref时公开给父组件的实例值
  • useMutationEffect 更新兄弟组件之前,它在React执行其DOM扭转的同一阶段同步触发
  • useLayoutEffect DOM扭转后同步触发。应用它来从DOM读取布局并同步从新渲染

(2)React16.9

  • 重命名 Unsafe 的生命周期办法。新的 UNSAFE_前缀将有助于在代码 review 和 debug 期间,使这些有问题的字样更突出
  • 废除 javascrip:模式的 URL。以javascript:结尾的URL 非常容易蒙受攻打,造成安全漏洞。
  • 废除”Factory”组件。 工厂组件会导致 React 变大且变慢。
  • act()也反对异步函数,并且你能够在调用它时应用 await。
  • 应用 <React.ProfiLer> 进行性能评估。在较大的利用中追踪性能回归可能会很不便

(3)React16.13.0

  • 反对在渲染期间调用setState,但仅实用于同一组件
  • 可检测抵触的款式规定并记录正告
  • 废除 unstable_createPortal,应用CreatePortal
  • 将组件堆栈增加到其开发正告中,使开发人员可能隔离bug并调试其程序,这能够分明地阐明问题所在,并更快地定位和修复谬误。

应用状态要留神哪些事件?

要留神以下几点。

  • 不要间接更新状态
  • 状态更新可能是异步的
  • 状态更新要合并。
  • 数据从上向下流动

**

React 与 Vue 的 diff 算法有何不同?

diff 算法是指生成更新补丁的形式,次要利用于虚构 DOM 树变动后,更新实在 DOM。所以 diff 算法肯定存在这样一个过程:触发更新 → 生成补丁 → 利用补丁。

React 的 diff 算法,触发更新的机会次要在 state 变动与 hooks 调用之后。此时触发虚构 DOM 树变更遍历,采纳了深度优先遍历算法。但传统的遍历形式,效率较低。为了优化效率,应用了分治的形式。将繁多节点比对转化为了 3 种类型节点的比对,别离是树、组件及元素,以此晋升效率。

  • 树比对:因为网页视图中较少有跨层级节点挪动,两株虚构 DOM 树只对同一档次的节点进行比拟。
  • 组件比对:如果组件是同一类型,则进行树比对,如果不是,则间接放入到补丁中。
  • 元素比对:次要产生在同层级中,通过标记节点操作生成补丁,节点操作对应实在的 DOM 剪裁操作。

以上是经典的 React diff 算法内容。自 React 16 起,引入了 Fiber 架构。为了使整个更新过程可随时暂停复原,节点与树别离采纳了 FiberNode 与 FiberTree 进行重构。fiberNode 应用了双链表的构造,能够间接找到兄弟节点与子节点。整个更新过程由 current 与 workInProgress 两株树双缓冲实现。workInProgress 更新实现后,再通过批改 current 相干指针指向新节点。

Vue 的整体 diff 策略与 React 对齐,尽管不足工夫切片能力,但这并不意味着 Vue 的性能更差,因为在 Vue 3 初期引入过,前期因为收益不高移除掉了。除了高帧率动画,在 Vue 中其余的场景简直都能够应用防抖和节流去进步响应性能。

diff 算法?

  • 把树形构造依照层级合成,只比拟同级元素
  • 给列表构造的每个单元增加惟一的 key 属性,不便比拟
  • React 只会匹配雷同 class 的 component(这外面的 class 指的是组件的名字)
  • 合并操作,调用 component 的 setState 办法的时候, React 将其标记为 dirty.到每一个 事件循环完结, React 查看所有标记 dirty 的 component 从新绘制.
  • 抉择性子树渲染。开发人员能够重写 shouldComponentUpdate 进步 diff 的性能。

diff算法如何比拟?

  • 只对同级比拟,跨层级的dom不会进行复用
  • 不同类型节点生成的dom树不同,此时会间接销毁老节点及子孙节点,并新建节点
  • 能够通过key来对元素diff的过程提供复用的线索
  • 单节点diff
  • 单点diff有如下几种状况:
  • key和type雷同示意能够复用节点
  • key不同间接标记删除节点,而后新建节点
  • key雷同type不同,标记删除该节点和兄弟节点,而后新创建节点

在 Redux中应用 Action要留神哪些问题?

在Redux中应用 Action的时候, Action文件里尽量放弃 Action文件的污浊,传入什么数据就返回什么数据,最妤把申请的数据和 Action办法分来到,以放弃 Action的污浊。

React Hooks 和生命周期的关系?

函数组件 的实质是函数,没有 state 的概念的,因而不存在生命周期一说,仅仅是一个 render 函数而已。
然而引入 Hooks 之后就变得不同了,它能让组件在不应用 class 的状况下领有 state,所以就有了生命周期的概念,所谓的生命周期其实就是 useStateuseEffect()useLayoutEffect()

即:Hooks 组件(应用了Hooks的函数组件)有生命周期,而函数组件(未应用Hooks的函数组件)是没有生命周期的

上面是具体的 class 与 Hooks 的生命周期对应关系

  • constructor:函数组件不须要构造函数,能够通过调用 **useState 来初始化 state**。如果计算的代价比拟低廉,也能够传一个函数给 useState
const [num, UpdateNum] = useState(0)
  • getDerivedStateFromProps:个别状况下,咱们不须要应用它,能够在渲染过程中更新 state,以达到实现 getDerivedStateFromProps 的目标。
function ScrollView({row}) {
  let [isScrollingDown, setIsScrollingDown] = useState(false);
  let [prevRow, setPrevRow] = useState(null);
  if (row !== prevRow) {
    // Row 自上次渲染以来产生过扭转。更新 isScrollingDown。
    setIsScrollingDown(prevRow !== null && row > prevRow);
    setPrevRow(row);
  }
  return `Scrolling down: ${isScrollingDown}`;
}

React 会立刻退出第一次渲染并用更新后的 state 从新运行组件以防止消耗太多性能。

  • shouldComponentUpdate:能够用 **React.memo** 包裹一个组件来对它的 props 进行浅比拟
const Button = React.memo((props) => {  // 具体的组件});

留神:**React.memo 等效于 **`PureComponent,它只浅比拟 props。这里也能够应用 useMemo` 优化每一个节点。

  • render:这是函数组件体自身。
  • componentDidMount, componentDidUpdateuseLayoutEffect 与它们两的调用阶段是一样的。然而,咱们举荐你一开始先用 useEffect,只有当它出问题的时候再尝试应用 useLayoutEffectuseEffect 能够表白所有这些的组合。
// componentDidMount
useEffect(()=>{
  // 须要在 componentDidMount 执行的内容
}, [])
useEffect(() => { 
  // 在 componentDidMount,以及 count 更改时 componentDidUpdate 执行的内容
  document.title = `You clicked ${count} times`; 
  return () => {
    // 须要在 count 更改时 componentDidUpdate(先于 document.title = ... 执行,恪守先清理后更新)
    // 以及 componentWillUnmount 执行的内容       
  } // 当函数中 Cleanup 函数会依照在代码中定义的程序先后执行,与函数自身的个性无关
}, [count]); // 仅在 count 更改时更新

请记得 React 会期待浏览器实现画面渲染之后才会提早调用 ,因而会使得额定操作很不便

  • componentWillUnmount:相当于 useEffect 外面返回的 cleanup 函数
// componentDidMount/componentWillUnmount
useEffect(()=>{
  // 须要在 componentDidMount 执行的内容
  return function cleanup() {
    // 须要在 componentWillUnmount 执行的内容      
  }
}, [])
  • componentDidCatch and getDerivedStateFromError:目前还没有这些办法的 Hook 等价写法,但很快会加上。
class 组件 Hooks 组件
constructor useState
getDerivedStateFromProps useState 外面 update 函数
shouldComponentUpdate useMemo
render 函数自身
componentDidMount useEffect
componentDidUpdate useEffect
componentWillUnmount useEffect 外面返回的函数
componentDidCatch
getDerivedStateFromError

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理