log 加爆查看 function 效率問題
- 第一次渲染:印出前三個。
- 第一次
store.dispatch
:印出中間三個。 - 第二次
store.dispatch
:印出最後三個
發現不用 renderContenet
以下的優化方法可行嗎?
1. 先判斷若傳入相同的數據就不渲染
function renderApp (newAppState, oldAppState = {}) { // 防止 oldAppState 没有傳入,所以加了默認參數 oldAppState = {}
if (newAppState === oldAppState) return
console.log('render app...')
renderTitle(newAppState.title, oldAppState.title)
renderContent(newAppState.content, oldAppState.content)
}
function renderTitle (newTitle, oldTitle) {
if (newTitle === oldTitle) return
console.log('render title')
const titleDOM = document.getElementById('title')
titleDOM.innerHTML = newTitle.text
titleDOM.style.color = newTitle.color
}
function renderContent (newContent, oldContent) {
if (newContent === oldContent) return
console.log('render content')
const contentDOM = document.getElementById('content')
contentDOM.innerHTML = newContent.text
contentDOM.style.color = newContent.color
}
2. 再將舊的狀態儲存起來
const store = createStore(appState, stateChanger)
let oldState = store.getState() // 儲存舊的 state
store.subscribe(() => {
const newState = store.getState() // 數據可能變化,獲取新的 state
renderApp(newState, oldState) // 把新舊的 state 傳進去渲染
oldState = newState // 渲染完成後,把新的 newState 變成舊的 oldState,等待下一次重新渲染
}) // 監聽數據變化
結果:這樣的方法行不通,因為改到相同的記憶體位置
查看 stateChanger
函式,發現是「修改相同記憶體位置上的東西」。
function stateChanger (state, action) {
switch (action.type) {
case 'UPDATE_TITLE_TEXT':
state.title.text = action.text
break
case 'UPDATE_TITLE_COLOR':
state.title.color = action.color
break
default:
break
}
}
以上改動的如同以下的程式碼
修改相同的記憶體位置的東西
let appState = {
title: {
text: 'React.js 小書',
color: 'red',
},
content: {
text: 'React.js 小書內容',
color: 'blue'
}
}
const oldState = appState
appState.title.text = '《React.js 小书》'
oldState !== appState // false
了解一下 ES6 解構
const obj = {a: 1, b: 2}
const obj2 = { ...obj } // => { a: 1, b: 2 }
const obj3 = { ...obj, b: 3, c: 4} // => { a: 1, b: 2, c: 3 }
obj、obj2、obj3 的記憶體位置不同
改變記憶體位置
let newAppState = {
...appState, // 複製 appState 裡面的內容
title: { // 用一個新的物件覆蓋原來 title 屬性
...appState.title,
text: '新的 React.js 小書' // 覆蓋 text 屬性
}
}
content 指的是原本的物件 (舊的記憶體位置),title 則不是
color 也是一樣
console.log('1. ', newAppState !== newAppState1) // ture
console.log('2. ', appState.title !== newAppState.title) // ture
console.log('3. ', appState.content !== appState.content) // false
優化性能
function stateChanger (state, action) {
switch (action.type) {
case 'UPDATE_TITLE_TEXT':
return {
...state,
title: {
...state.title,
text: action.text
}
}
case 'UPDATE_TITLE_COLOR':
return {
...state,
title: {
...state.title,
color: action.color
}
}
default:
return state // 沒有修改,回傳原來的狀態
}
}
修改一下 createStore
每次呼叫 stateChanger(state, action)
後覆蓋原來的物件。
function createStore (state, stateChanger) {
const listeners = []
const subscribe = (listener) => listeners.push(listener)
const getState = () => state
const dispatch = (action) => {
state = stateChanger(state, action) // 覆盖原对象
listeners.forEach((listener) => listener())
}
return { getState, dispatch, subscribe }
}
只有第一次印出 render content
本節完整程式碼
const appState = {
title: {
text: 'React.js 小書',
color: 'red',
},
content: {
text: 'React.js 小書內容',
color: 'blue'
}
}
let newAppState = {
...appState, // 複製 appState 裡面的內容
title: { // 用一個新的物件覆蓋原來 title 屬性
...appState.title, // 複製原來 title 裡面的內容
text: '新的 React.js 小書' // 覆蓋 text 屬性
}
}
let newAppState1 = {
...newAppState, // 複製 newAppState 裡面的內容
title: { // 用一個新的物件覆蓋原來 title 屬性
...newAppState.title, // 複製原來 title 裡面的內容
text: '新的 React.js 小書' // 覆蓋 color 屬性
}
}
console.log('1. ', newAppState !== newAppState1) // ture
console.log('2. ', appState.title !== newAppState.title) // ture
console.log('3. ', appState.content !== appState.content) // false
// 新增渲染函數
function renderApp (newAppState, oldAppState = {}) { // 防止 oldAppState 没有傳入,所以加了默認參數 oldAppState = {}
if (newAppState === oldAppState) return
console.log('render app...')
renderTitle(newAppState.title, oldAppState.title)
renderContent(newAppState.content, oldAppState.content)
}
function renderTitle (newTitle, oldTitle) {
if (newTitle === oldTitle) return
console.log('render title')
const titleDOM = document.getElementById('title')
titleDOM.innerHTML = newTitle.text
titleDOM.style.color = newTitle.color
}
function renderContent (newContent, oldContent) {
if (newContent === oldContent) return
console.log('render content')
const contentDOM = document.getElementById('content')
contentDOM.innerHTML = newContent.text
contentDOM.style.color = newContent.color
}
// createStore
function createStore (state, stateChanger) {
const listeners = []
const subscribe = (listener) => listeners.push(listener)
const getState = () => state
const dispatch = (action) => {
state = stateChanger(state, action)
listeners.forEach((listener) => listener())
}
return { getState, dispatch, subscribe }
}
function stateChanger (state, action) {
switch (action.type) {
case 'UPDATE_TITLE_TEXT':
return {
...state,
title: {
...state.title,
text: action.text
}
}
case 'UPDATE_TITLE_COLOR':
return {
...state,
title: {
...state.title,
color: action.color
}
}
default:
return state // 沒有修改,回傳原來的狀態
}
}
const store = createStore(appState, stateChanger)
let oldState = store.getState() // 儲存舊的 state
store.subscribe(() => {
const newState = store.getState() // 數據可能變化,獲取新的 state
renderApp(newState, oldState) // 把新舊的 state 傳進去渲染
oldState = newState // 渲染完成後,把新的 newState 變成舊的 oldState,等待下一次重新渲染
}) // 監聽數據變化
renderApp(store.getState()) // first render
store.dispatch({type: 'UPDATE_TITLE_TEXT', text: 'React 小書'}) // 修改內容
store.dispatch({type: 'UPDATE_TITLE_COLOR', color: 'blue'}) // 修改標題顏色
// renderApp(store.getState()) // redner again
總結
- 在渲染函式中新增判斷式
- 在
stateChanger
函式中複製相同內容但改變記憶體位置。 - createStore 中將舊的狀態 state 變成新的狀態 state。
藉由判斷是否改變記憶體、是否傳入相同的數據,決定要不要重新渲染。
不必擔心新增記憶體位置會有性能、內存問題,因為成本非常低。而且最多保存兩個物件引用 oldState
、newState
,其餘的會被回收掉。