Vue和React的檢視更新機制對比

deswan_發表於2018-12-17

Vue和React的其中一個最重要的區別是它們對於資料更新的管理方式不同,Vue基本上是一套基於getter/setter實現的依賴收集/依賴更新的訂閱式機制,而React則是通過顯式的觸發函式呼叫來更新資料,比如setState。相比來說Vue的實現方式更細粒度一些, 通過依賴收集,Vue是能夠知道一些資料的更新導致了哪些地方需要重新計算的,通過這種機制,Vue能夠優雅地實現計算屬性、watch,包括檢視渲染。而React由於缺少這種細粒度的機制,則更多時候需要一些其它方案來提高效能,於是產生了如PureComponent、ImmutableJS、shouldComponentUpdate鉤子等等。

Vue和React如何更新檢視?

  • Vue:賦值data,如this.value = 3

  • React:this.setState({value: 3})

其中的區別在於,Vue知道是元件資料中的value欄位發生更新了, 而React只知道是元件的State發生了變化,並不知道是什麼資料發生了變化。

Vue的檢視重渲染

Vue的訂閱式機制決定了它不僅知道哪些資料發生了更新,也知道這個資料更新了之後當前元件以及子元件的檢視需不需要重新渲染。這是通過“依賴收集”實現的,Vue的檢視template會編譯成render函式,在資料(data/props/computed)中定義了getter,每次呼叫各個元件的render函式時,通過getter,就能知道哪些資料被哪些元件的檢視所依賴,下一次對這些資料賦值時,也就是呼叫setter,相應的檢視就能觸發重渲染,而無關的元件則不需要再次呼叫render函式,節省了開銷。借用Vue作者做的圖:(他稱之為push式更新)

vue-push-rendering

舉個例子:子元件Child使用了propsvalue進行渲染,父元件Parent將datavalue作為props傳遞給了子元件Child,一秒後更新data。

// Child.vue
<script>
export default {
  name: "Child",
  props: ["value"],
  render(h) {
    console.log("Child render");
    return h("div", this.value);
  }
};
</script>

// Parent.vue
import Child from "./components/Child";

export default {
  data() {
    return {
      value: 1
    };
  },
  components: { Child },
  created() {
    setTimeout(() => this.value = 2, 1000);
  },
  render(h) {
    console.log("Parent render");
    return h("div", [
      h(Child, {
        props: { value: this.value }
      })
    ]);
  }
};
</script>
複製程式碼

控制檯列印:

Parent render
Parent render
//after 1000ms
Child render
Child render
複製程式碼

由於Parent元件和Child元件的檢視都使用了Parent的datavalue,因此改變value的值,父子元件都會進行重渲染。

情況二: 不將value作為props傳給子元件Child,只用於元件自身檢視:

<script>
// Child.vue
<script>
export default {
  name: "Child",
  props: ["value"],
  render(h) {
    console.log("Child render");
    return h("div", this.value);
  }
};
</script>

// Parent.vue
import Child from "./components/Child";

export default {
  data() {
    return {
      value: 1
    };
  },
  components: { Child },
  created() {
    setTimeout(() => this.value = 2, 1000);
  },
  render(h) {
    console.log("Parent render");
    return h("div", [
      h(Child),
      this.value + ""
    ]);
  }
};
</script>
複製程式碼

控制檯列印:

Parent render
Child render
//after 1000ms
Parent render 
複製程式碼

改變value後,父元件重渲染了,而子元件沒有重渲染,因為子元件的render函式沒有收集到Parent元件的資料value的依賴。

React的重渲染

當呼叫了setState,React並不在乎有什麼資料發生了改變,接著觸發元件的shouldComponentUpdate,如果返回true則呼叫render,然後以同樣的辦法依次更新所有子元件,如果返回false則阻止render方法呼叫及子元件更新。換句話說,更新檢視的控制權由shouldComponentUpdate掌握,而預設情況下該方法返回true。(Vue作者稱為pull式更新):

react-pull-rendering

看個例子:

function Child(props) {
  console.log("Child render");
  return <div />;
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 1
    };
  }
  componentDidMount() {
    setTimeout(() => {
      this.setState({
        value: 2
      });
    }, 1000);
  }
  render() {
    console.log("Parent render");
    return (
      <div className="App">
        <Child />
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
複製程式碼

控制檯列印:

Parent render
Child render
Parent render
Child render
複製程式碼

甚至檢視什麼資料都沒使用,呼叫setState的元件及其子元件就都重渲染了。當元件或節點比較多的時候,更新資料可能會造成很多不必要的虛擬DOM的構建,龐大的節點樹也拖慢了diff的速度,這時就需要引入一些優化方案,比如PureComponent配合ImutableJS, PureComponent利用propsstate屬性的淺對比來決定要不要重渲染,如果淺對比結果是相等,則元件及其子元件不參與重渲染。

相關文章