React的几个重要概念(一定要熟悉)

React的核心思想:view是state的输出

view = f(state)

上面的f代表函数关系,只要state发生变化,view就会相应的改变。

React 的本质是将图形界面(GUI)函数化。

key的必要性与重要性

React中渲染数组的时候,一定要加上key属性。

key必须是字符串类型,它的取值可以用数据对象的某个唯一属性,或是对数据进行hash来生成key。用来标记同父同层级的兄弟元素。只要子元素有key属性,便会去原V-dom树中相应位置寻找是否有相同key的元素,比较它们是否完全相同,如果相同的话就直接复用该元素,提升渲染性能。

React的diff算法是通过js对象默认dom树,对新旧dom同层级的元素挨个比较。

diff算法把树按照层级进行分解,只比较同级元素。只会匹配组件名称相同的component,(class 名称相同),例如:组件<Header />被组件<NewHeader />替换了,react会把旧组件删除掉,直接创建一个新的组件<NewHeader />,不会浪费时间去比较两个不可能有相似之处的component。大大提高了效率。

// 旧v-dom
<ul>
    <li key="1">first</li>
    <li key="2">second</li>
</ul>

// 新v-dom
<ul>
    <li key="0">阿萨德</li>
    <li key="1">first</li>
    <li key="2">second</li>
</ul>

react通过diff算法比较发现,只是新增了key为“0”的元素,其他2个只是调换了一下位置,只需要给它们2个打上一个调换顺序的标记即可。

但是强烈不推荐用数组index来作为key。如果数据更新仅仅是数组重新排序或在其中间位置插入新元素,那么视图元素都将重新渲染。来看下例子:

// 旧v-dom
<ul>
    <li key="0">first</li>
    <li key="1">second</li>
</ul>

// 新v-dom
<ul>
    <li key="0">阿萨德</li>
    <li key="1">first</li>
    <li key="2">second</li>
</ul>

React发现key为0,1,2的元素的text都变了,将会新建dom节点去替换原来的节点,这样的话key就失去了意义。而如果用唯一id,react会知道同key节点没有变化,只是换了位置,只要打个移动节点的patch到dom上,而不是新建替换

react性能优化

  • 减少setState的使用。因为每调用一次,就会重新计算一次子树
  • 通过使用shouldComponentUpdate减少大的子树的重新计算,避免不必要的渲染
  • 通过key标记列表中的子元素,提高diff算法的性能

react+redux的工作流程

  • React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。UI组件负责UI的渲染,没有状态,参数通过props提供;容器组件负责管理数据和业务逻辑,带有内部状态。
  • 通过Provider拿到store,Provider的作用就是让容器组件获取到store,store放在context属性中
  • connect把UI组件生成容器组件。参数有两个(mapStateToProps, mapDispatchToProps),这2个参数就定义了组件的业务逻辑。第一个是输入逻辑:把state映射到UI组件的参数,第二个是输出逻辑:把用户发出的动作映射到action,并从UI组件发出去。
  • connect生成容器组件之后,需要让容器组件拿到state对象,才能生成UI组件的参数。而Provider就是实现这一功能。
  • 在根组件外面包一层Provider,这样子组件就可以都能拿到state
  • Reducer对应的是项目中的ViewModel,通过createStore(Reducer)生成store
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import { Provider, connect } from 'react-redux'

// React component
class Counter extends Component {
  render() {
    const { value, onIncreaseClick } = this.props
    return (
      <div>
        <span>{value}</span>
        <button onClick={onIncreaseClick}>Increase</button>
      </div>
    )
  }
}

Counter.propTypes = {
  value: PropTypes.number.isRequired,
  onIncreaseClick: PropTypes.func.isRequired
}

// Action
const increaseAction = { type: 'increase' }

// Reducer
function counter(state = { count: 0 }, action) {
  const count = state.count
  switch (action.type) {
    case 'increase':
      return { count: count + 1 }
    default:
      return state
  }
}

// Store
const store = createStore(counter)

// Map Redux state to component props
function mapStateToProps(state) {
  return {
    value: state.count
  }
}

// Map Redux actions to component props
function mapDispatchToProps(dispatch) {
  return {
    onIncreaseClick: () => dispatch(increaseAction)
  }
}

// Connected Component
const App = connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter)

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

虚拟dom为什么能提升性能(必考)

如何理解虚拟dom
虚拟DOM相当于在js对象和真实DOM树之间加了一个缓存,利用diff算法避免了一些不必要的dom操作,提升了性能。

  • 利用js对象结构表示dom树的结构,然后用这个树构建一个真正的dom树,插入到文档当中
  • 当状态变化的时候就重新渲染一个新的虚拟DOM树,然后利用diff算法比较新树和旧树之间的差别
  • 把二者之间的差异应用到步骤1中的真实dom树上,视图就更新了。

用js对象模拟dom树结构很简单,只需要记录她的节点类型、属性、以及子节点即可。

var element = {
  tagName: 'ul', // 节点标签名
  props: { // DOM的属性,用一个对象存储键值对
    id: 'list'
  },
  children: [ // 该节点的子节点
    {tagName: 'li', props: {class: 'item'}, children: ["Item 1"]},
    {tagName: 'li', props: {class: 'item'}, children: ["Item 2"]},
    {tagName: 'li', props: {class: 'item'}, children: ["Item 3"]},
  ]
}

对应的HTML是这样的

<ul id='list'>
  <li class='item'>Item 1</li>
  <li class='item'>Item 2</li>
  <li class='item'>Item 3</li>
</ul>

上面说的节点的差异指的是什么呢?对 DOM操作可能会:

  • 替换掉原来的节点,例如把上面的div换成了section
  • 移动、删除、新增子节点,例如上面div的子节点,
  • 把p和ul顺序互换修改了节点的属性对于文本节点,
  • 文本内容可能会改变。例如修改上面的文本节点2内容为Virtual DOM 2。

React diff算法原理(常考,大厂必考)

参考:diff算法

diff算法原理

react diff算法

  • 把生成的树形结构的虚拟dom按层级分解,只比较同级元素
  • 给列表元素增加表示唯一性的标记符key属性,方便比较
  • React只会匹配相同class的component(这里的class指的是组件名字)
  • 合并操作,调用 component的setState方法的时候,React会将其标记为dirty,在一次事件循环结束的时候,react会统计所有被标记为dirty的component,并重新绘制。
  • 选择性子树渲染。可在shouldComponentUpdate方法中编写高性能的逻辑,提升性能

React中refs的作用:

Refs 是 React 提供给我们的安全访问 DOM 元素或者某个组件实例的句柄。我们可以为元素添加 ref 属性然后在回调函数中接受该元素在 DOM 树中的句柄,该值会作为回调函数的第一个参数返回:

class CustomForm extends Component {
  handleSubmit = () => {
    console.log("Input Value: ", this.input.value)
  }
  render () {
    return (
      <form onSubmit={this.handleSubmit}>
        <input
          type='text'
          ref={(input) => this.input = input} />
        <button type='submit'>Submit</button>
      </form>
    )
  }
}

调用setState方法之后发生了什么

参考

在代码中调用 setState 函数之后,React 会将传入的参数对象与组件当前的状态合并,然后触发所谓的调和过程(Reconciliation)。经过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树并且着手重新渲染整个 UI 界面。在 React 得到元素树之后,React 会自动计算出新的树与老树的节点差异,然后根据差异对界面进行最小化重渲染。在差异计算算法中,React 能够相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染。

要知道setState本质是通过一个队列机制实现state更新的。 执行setState时,会将需要更新的state合并后放入状态队列,而不会立刻更新state,队列机制可以批量更新state。
如果不通过setState而直接修改this.state,那么这个state不会放入状态队列中,下次调用setState时对状态队列进行合并时,会忽略之前直接被修改的state,这样我们就无法合并了,而且实际也没有把你想要的state更新上去。

import React, {Component} from "react";

export default class Example extends React.Component {
    constructor() {
        super();
        this.state = {
            val: 0
        };
    }
    
     componentWillMount() {
        this.setState({val: this.state.val + 1});
        console.log(this.state.val);    // 第 1 次 log  0

        this.setState({val: this.state.val + 1});
        console.log(this.state.val);    // 第 2 次 log
    }

    componentDidMount() {
        this.setState({val: this.state.val + 1});
        console.log(this.state.val);    // 第 1 次 log

        this.setState({val: this.state.val + 1});
        console.log(this.state.val);    // 第 2 次 log

        this.setState({
            val: this.state.val + 6
        }, function(){
            console.log(3, this.state.val);
        });
    
        setTimeout(() => {
            console.log(this.state.val);  // 第 3 次 log
            this.setState({val: this.state.val + 1});
            console.log(this.state.val);  // 第 3 次 log

            this.setState({val: this.state.val + 1});
            console.log(this.state.val);  // 第 4 次 log
        }, 0);
    }

    render() {
        return (
            <div>
                12asf
            </div>
        );
    }
};

最终的结果是 0 0 1 1 7 7 8 9
原因是,在setTimeout中,state已经更新,拿到的值是之前最后一次更新的数据(即 1)。

setState在原生事件,setTimeout,setInterval,Promise等异步操作中,state会同步更新(立即改变)

setStatePromise(updator) {
    return new Promise(((resolve, reject) => {
        this.setState(updator, resolve);
    }));
}

componentWillMount() {
    this.setStatePromise(({ num }) => ({
        num: num + 1,
    })).then(() => {
        console.log(this.state.num);
    });
}

传入第二个参数(回调函数),在状态更新成功之后调用,此时取到的state已经是更新之后的值

        this.setState({
            val: this.state.val + 6
        }, function(){
            console.log(this.state.val); // 更新之后的state
        });
  • componentDidMount 中拿到的state也是更新后的数据

state会在每次render执行之后更新state,所以render之后的声明周期函数取到的state都是已经更新过的。

setState和replaceState的区别

setState是修改其中的部分状态,相当于Object.assign,只是覆盖,不会减少原来的状态
replaceState是完全替换原来的状态,相当于赋值,将原来的state替换为另一个对象,如果新状态属性减少,那么state中就没有这个状态了

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值