react中使用usestate踩坑及解決

usestate的常規用法

在react框架中,不適用類組件,使用函數式組件又想自定義數據維護業務開發的時候,就需要使用react提供的hook來完成。usestate就是最常見的一種hook。

const [name,setName] = useState('dx');
setName('dx1')

中括號實際是一個解構運算,第一個name是設置的值,第二個setName是隻能用來改變name的方法。

useState遇到的坑

1、useState不適合復雜對象的更改

因為useState不能像setState那樣進行合並更新,當使用useState第二個參數進行數據更新的時候,必須傳入一個完整的結構,而不僅僅隻是改變的那一部分。

如果你想讓一個復雜的對象都能實現響應,可以分兩種情況。

第一種情況,這個復雜的對象每次都是整體發生改變,那麼也可以直接使用useState。

第二種情況,你隻是想讓許多的簡單數據都放到一個對象裡面,這樣便於統一管理,那我建議,如果這些簡單數據之間都沒什麼必然聯系的話,還是分開創建多個state更好。

在編碼的過程中,我們寧願以空間復雜度換取時間復雜度,多創建幾個變量和創建一個變量,在用戶體驗上並不會有太多的差別。

但如果數據過於復雜,diff算法找到對應的變化及發生響應,大規模的重新渲染,這一過程,將會導致用戶體驗下降。

2、useState異步回調的問題

當使用usestate對數據進行更新,並不能立刻獲取到最新的數據。

  const [name, setName] = useState('dx');

  const handleTest = () => {
    console.log(name) // dx
    setName('dx1')
    console.log(name) // dx
  }

解決的辦法。

一、配合useEffect使用

  const [name, setName] = useState('dx');
  const handleTest = () => {
    console.log(name) //dx
    setName('dx1')
    console.log(name)//dx
  }
  
  useEffect(() => {
    console.log(name) //dx1
  },[name])

二、創建一個新的變量保存最新的數據

  const [name, setName] = useState('dx');
  const handleTest = () => {
    console.log(name) //dx
    const newName = "dx1"
    setName(newName)
    console.log(newName) //dx1
  }

三、用一個函數包裹,不推薦使用,因為函數裡面所有的東西都會全部重新定義

const [name, setName] = useState('dx');
 function text () {
   const handleTest = () => {
     console.log(name) //dx
     const newName = "dx1"
     setName(newName)
     console.log(name) //dx
     console.log(newName) //dx1
   }
   useEffect(() => {
     console.log(name) //dx1
   },[name])

   return (
     <div>
       {name} //點擊之前dx,點擊按鈕之後dx1
      <button type="button" onClick={handleTest }>改變名字</button>
     </div>
   )
 }
console.log(name) //點擊按鈕之前dx,點擊按鈕之後dx1

3、根據hook的規則,使用useState的位置有限制

強調,所有的hook和自定義hook都遵循此規則。

僅頂層調用 Hook :不能在循環,條件,嵌套函數等中調用useState()。

在多個useState()調用中,渲染之間的調用順序必須相同。

僅從React 函數調用 Hook:必須僅在函數組件或自定義鉤子內部調用useState()。

4、使用useState,回調函數形式更改數據

const [a, setA] = useState({c:1})
/** oldA為之前的a,return為設置的新值 */
setA((oldA) => {
return {c: oldA.c + 1}
})

5、useState存入的值隻是該值的引用(引用類型)

const textObj = {name:'dx'}

const [useState1, setUseState1] = useState(textObj )

const [useState2, setUseState2] = useState(textObj )
/** usestate的操作不要放在函數的最外層,這裡隻是簡單的代碼展示,你可以將set操作放在某個函數裡面 */
setUseState1((oldUseState1) => {
	oldUseState1.age = 18
return {...oldUseState1}
})

useEffect(() => {
	/** 改變一個會導致兩個都改變,深淺拷貝的問題 */
	console.log(useState1)  // {name: "dx", age: 18}
	console.log(useState2)  // {name: "dx", age: 18}
},[
useState1
])

解決的方案

const textObj = {name:'dx'}

const [useState1, setUseState1] = useState(textObj )

const [useState2, setUseState2] = useState(JSON.parse(JSON.stringify(textObj)))
/** usestate的操作不要放在函數的最外層,這裡隻是簡單的代碼展示,你可以將set操作放在某個函數裡面 */
setUseState1((oldUseState1) => {
	oldUseState1.age = 18
return {...oldUseState1}
})

useEffect(() => {
	/** 改變一個會導致兩個都改變,深淺拷貝的問題 */
	console.log(useState1)  // {name: "dx", age: 18}
	console.log(useState2)  // {name: "dx"}
},[
useState1
])

6、useState,如果保存引用數據,useEffect檢測不到變化?

const textObj = {name:'dx'}
const [useState1, setUseState1] = useState(textObj)
/** usestate的操作不要放在函數的最外層,這裡隻是簡單的代碼展示,你可以將set操作放在某個函數裡面 */
setUseState1((oldUseState1) => {
	oldUseState1.age = 18
return oldUseState1

useEffect(() => {
	console.log(useState1)  
},[
useState1
])
//結果是沒有任何反應

解決方法

const textObj = {name:'dx'}
const [useState1, setUseState1] = useState(textObj)
/** usestate的操作不要放在函數的最外層,這裡隻是簡單的代碼展示,你可以將set操作放在某個函數裡面 */
setUseState1((oldUseState1) => {
	oldUseState1.age = 18
	/** 返回一個新的對象,useEffectc才能檢測得到 */
return {...oldUseState1}

useEffect(() => {
	console.log(useState1)  // {name: "dx", age: 18}
},[
useState1
])

7、useState無法保存一個函數

你是否嘗試著將函數的引用作為一個變量保存到useState中去呢?

比如:

  const testFunciton1 = () => {
    console.log({name: 'dx',age: '18'})
  }

  /** usestate保存函數測試 */
  const [stateFunction, setstateFunction] = useState<() => void>(testFunciton1);

  useEffect(() => {
   console.log(stateFunction)
  }, [stateFunction])

打印結果

代碼中從未調用過testFunciton1 ,結果testFunciton1卻被執行瞭

useEffect打印出來的卻是一個undefined。

稍微改動一下代碼,再測試

  const testFunciton1 = () => {
    console.log({name: 'dx',age: '18'})
    return {
      name: 'yx',
      age: '17'
    }
  }

  /** usestate保存函數測試 */
  const [stateFunction, setstateFunction] = useState<() => void>(testFunciton1);

  useEffect(() => {
   console.log(stateFunction)
  }, [stateFunction])

結果

很明顯,在useState中,函數會自動調用,並且保存函數返回的值,而不能保存函數本身。

解決的方案

使用useState不能保存函數,那麼可以使用useCallback這個hook。

  /** useCallback,使用參數與useEffect一致 */
  const testFunction = useCallback(() => {
    // useCallback返回的函數在useCallbak中構建
    const testFunciton1 = () => {
      console.log({ name: 'dx', age: '18' });
      return {
        name: 'yx',
        age: '17',
      };
    };
    return testFunciton1;
  }, []);

  useEffect(() => {
    console.log(testFunction());
  }, [testFunction]);

結果

useState實現原理

前一段時間面試某大廠的時候,講到瞭useState這個hook,要求簡單寫一下useState的實現原理,以下代碼隻是一個粗淺的原理。

function useState(init) {
	let state;
	// useState無法保存函數
	if(typeof init === 'function') {
		state = init()
	} else {
		state = init
	}

	const setState = (change) => {
		// 判斷一下是否傳遞過來的是函數
		if(typeof change === 'function') {
			// 如果是函數,調用,並將之前的state傳過去,接收到的返回值作為新的state並賦值
			state = change(state)
		} else {
			// 如果不是函數,直接賦值
			state = change;
		}
	}	
	return [state, setState]
}

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

推薦閱讀: