vue和react中props變化後如何修改state

vue和react中props變化後修改state

如果隻想在 state 更改時重新計算某些數據,比如搜索框案例。

vue

<template>
  <div>
  	<input type="text" v-model="filterText">
    <ul>
      <li v-for="item in filteredList" :key="item.id">
        {{ item.text }}
      </li>
    </ul>
  </div>
</template>
<script>
export default {
  props: {
    list: {
      type: Array,
      default: () => ([])
    }
  },
  data () {
    return {
      filterText: ''
    }
  },
  computed: {
    filteredList () {
      return this.list.filter(item => item.text.includes(this.filterText))
    }
  }
}
</script>

react

import React, { PureComponent } from 'react';
class Example extends PureComponent {
  state = {
    filterText: ''
  };
  handleChange = event => {
    this.setState({
      filterText: event.target.value
    })
  }
  render() {
    const filteredList = this.filter(this.props.list, this.state.filterText)
    return (
      <>
        <input
          type="text"
          onChange={this.handleChange}
          value={this.state.filterText} />
        <ul>
          {
            filteredList.map(
              item => <li key={item.id}>{item.text}</li>
            )
          }
        </ul>
      </>
    );
  }
}

如果你想在 prop 更改時“重置”某些 state,比如隨機默認值案例

vue

Vue提供瞭一種更通用的方式來觀察和響應Vue實例上的數據變動:偵聽屬性 watch。

<template>
  <div>
    <input type="text" v-model="text">
  </div>
</template>
<script>
export default {
  props: {
    email: {
      type: String,
      default: ''
    }
  },
  data () {
    return {
      text: ''
    }
  },
  watch: {
    email: {
      immediate: true,
      handler (value) {
        this.text = value
      }
    }
  }
}
</script>

react

React生命周期 getDerivedStateFromProps 會在調用 render 方法之前調用,並且在初始掛載及後續更新時都會被調用。它應返回一個對象來更新 state,如果返回 null 則不更新任何內容。

父組件重新渲染時觸發,請註意,不管原因是什麼,都會在每次渲染前觸發此方法。

class Example extends Component {
  state = {
    text: ''
  };
  handleChange = (event) => {
    this.setState({
      text: event.target.value
    })
  }
  static getDerivedStateFromProps(nextProps, prevState) {
    if (prevState.email !== nextProps.email) {
      return {
        text: nextProps.email,
        email: nextProps.email
      }
    }
    return {text: prevState.text}
  }
  render() {
    return (
      <>
        <input
          type="text"
          onChange={this.handleChange}
          value={this.state.text} />
      </>
    );
  }
}

改進

直接復制 prop 到 state 是一個非常糟糕的想法。這兩者的關鍵在於,任何數據,都要保證隻有一個數據來源,而且避免直接復制它。

vue

<template>
  <div>
    <input type="text" :value="value" @input="handleInput">
  </div>
</template>
<script>
export default {
  props: {
    value: {
      type: String,
      default: ''
    }
  },
  methods: {
    handleInput (e) {
      this.$emit('input', e.target.value)
    }
  }
}
</script>
<template>
  <div id="app">
    <Example v-model="email"/>
    <button @click="handleClick">默認值</button>
  </div>
</template>
<script>
import Example from './components/Example.vue'
export default {
  components: {
    Example
  },
  data () {
    return {
      email: ''
    }
  },
  methods: {
    handleClick () {
      this.email = String(Math.random())
    }
  }
}
</script>

react

function Example (props) {
  return <input onChange={props.onChange} value={props.email} />;
}
class App extends React.Component {
  state = {
    email: ''
  }
  handleClick = () => {
    this.setState({
      email: String(Math.random())
    })
  }
  handleChange = (event) => {
    this.setState({
      email: event.target.value
    })
  }
  render() {
    return (
      <>
        <Example email={this.state.email} onChange={this.handleChange} />
        <div>
          <button onClick={this.handleClick}>默認值</button>
        </div>
      </>
    );
  }
}

react改變state必須知道的知識點

react可以通過this.state.xx的方式直接獲取state,但是當我們修改state的時候,往往有許多的坑。

1.不能直接修改state

組件修改state,並不會重新觸發render。例如:

 //錯誤
this.state.title='attend';
//正確
this.setState({title:'attend'});

2.state的更新是異步的

調用setState時,組件state並不會立即改變,隻是把要修改的狀態放入事件隊列當中,為瞭彌補這個問題,使用另一種 setState() 的形式,接受一個函數。

這個函數將接收前一個狀態作為第一個參數,應用更新時的 props 作為第二個參數,代碼如下:

//正確
this.setState((prevState, props)=>({
    counter: prevState.counter + 1
}))

3.state的更新是一個合並的過程

當調用setState()修改組件的狀態時,隻需要傳入發生改變的state,而不是完整的state,因為組件state的更新是一個合並的過程:

this.state = {
    title: 'React',
    content: 'React is an wondeful JS library'
}

當隻需要修改title時,隻需要將修改的title傳給setState即可:

this.setState({title:'ReactJs'});

react會合並最新的title到原來的狀態,同時保留原來狀態的content,最終合並state為:

this.state = {
    title: 'ReactJs',
    content: 'React is an wondeful Js library'
}

state與不可變對象

react官方把state當成不可變對象,一方面直接修改this.state,組件並不會重新render;另一方面,state中包含的所有狀態都應該是不可變的對象,state當中的某一個狀態發生變化時,應該重新創建這個狀態對象,而不是直接修改原來的state狀態,那麼當狀態發生變化時,如何去創建新的狀態呢,我們根據狀態類型可以分為下面三種情況:

狀態類型為不可變類型

number、string、boolean、null、undefined

這種情況最簡單,因為狀態是不可變類型,所以直接給要修改的狀態賦一個新值即可,例如我們要修改的count為number型,title(string),success(boolean)三個狀態:

this.setState({
    count:1,
    title:'React',
    success:true
})

狀態類型為數組

假如有一個數組類型的狀態books,當向books中增加一本書時。

//方法一:使用preState,concat創建新數組
 this.setState((prevState)=>({
    books: prevState.books.concat(['React Guide'])
}))
//方法二:ES6 spread syntax
this.setState(prevState=>({
    books:[...prevState,'React Guide']
}))

當我們從books中截取部分元素作為新狀態時,可以用數組的slice方法:

this.setState(prevState=>({
    books: prevState.books.slice(1,3);
}))

當從books中過濾部分元素後,作為新狀態時,可以使用filter方法:

this.setState(prevState=>({
    books: prevState.books.filter(item=>{
      return item!='React';
    })
}))

【註意】不要使用push,pop,shift,unshift,splice等方法修改數組類型的狀態,因為這些方法都是在原數組的基礎上修改的,而concat,slice,filter會返回一個新的數組。

狀態的類型是普通對象

(1) 使用es6的Object.assgin()方法

this.setState({
    onwer: Object.assgin({},preState.onwer,{name:'Jason'});
})

(2) 使用對象擴展語法(Object spread properties):

this.setState(preState=>{
    owner: {...preState.owner, name:'Jason'}
})

總結

創建新的狀態的關鍵是避免使用直接修改原對象的方法,這種方法在vue中稱為變異方法,而是使用可以返回一個新對象的方法,當然可以使用Immutable的JS庫(Immutable.js)實現類似的效果。

思考

為什麼React推薦組件狀態的修改是不可變對象呢?

(1) 不可變對象的修改會返回一個新的對象,不用擔心原對象在不小心的情況下修改導致的錯誤,方便程序的管理和調試。

(2) 處於性能的考慮,對象組件的狀態是不可變對象時,在組件的shouldComponentUpdate方法中僅需要比較前後兩次狀態對象的引用就可以判斷狀態是否真的改變,從而避免不必要的render調用。

進階

除瞭以上方法改變react組件的狀態之外,我們還經常會用到replaceState()改變組件的狀態。

replaceState()方法與setState()類似,但是方法隻會保留nextState中狀態,原state不在nextState中的狀態都會被刪除。

//使用語法:
replaceState(object nextState[, function callback])
  • nextState,將要設置的新狀態,該狀態會替換當前的state。
  • callback,可選參數,回調函數。該函數會在replaceState設置成功,且組件重新渲染後調用。

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: