React新特性-Hooks之useState(四)

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

系列目录

为什么要使用Hook

Hook 是什么? Hook 是一个特殊的函数,它可以让你“钩入” React 的特性。

在组件之间复用状态逻辑很难

  • 缺少复用机制
  • 渲染属性和高阶组件导致层级冗余

复杂组件变得难以理解

  • 生命周期函数混杂不相干逻辑
  • 相干逻辑分散在不同生命周期

难以理解的 class

  • this 指向困扰
  • 内联函数过度创建新句柄

使用Hook的优势

  • 函数组件无 this 问题(拥抱函数式编程)
  • 自定义 Hook 方便复用状态逻辑
  • 副作用的关注点分离

使用useState

你可以把useState理解为以前class组件的this.setStateuseState 是允许你在 React 函数组件中添加 state 的 Hook。

来对比一下使用class和Hook的区别

import React, { useState } from 'react'

//未使用React Hook class组件
export default class TestInput extends React.Component {
  //初始化state
  state = {
    inputValue: '默认值'
  }
  render() {
    const { inputValue } = this.state;
    return (
      <div>
        <input value={inputValue} onChange={(e) => this.setState({ inputValue: e.target.value })} /> {inputValue}
      </div>
    )
  }
}

//使用React Hook 函数组件
const TestInput= () => {
  //声明和初始化state
  const [inputValue,setInputValue] = useState('默认值')
  return (
    <div>
      <input value={inputValue} onChange={(e)=>setInputValue(e.target.value)} /> {inputValue}
    </div>
  )
}
export default TestInput

它定义一个 “state”的变量。这里useState接收了一个默认值,useState返回的是一个数组。

数组里面总共是两个元素[“默认值”,匿名函数],这个匿名函数就相遇于class组件中的this.setState(),只不过匿名函数中是改变单个变量的值。

useState返回了两个元素,我们使用了es6中的数据解构的方式去赋值给inputValuesetInputValue

当调用了setInputValue之后,整个TestInput会重新渲染,在后续的重新渲染中,useState 返回的第一个值将始终是更新后最新的inputValue的值。

useState使用技巧

声明多个 state 变量

function TestInput() {
  // 声明多个 state 变量!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
  // ...
}

我们可以在单个组件中使用多个 State Hook ?答案是 React 靠的是 Hook 调用的顺序。因为我们的示例中,Hook 的调用顺序在每次渲染中都是相同的,所以它能够正常工作

function TestInput() {
  // ------------
  // 首次渲染
  // ------------
  const [age, setAge] = useState(42);             // 1. 使用 42 初始化变量名为 age 的 state
  const [fruit, setFruit] = useState('banana');   // 2. 使用 banana 初始化变量名为 fruit 的 state

  // -------------
  // 二次渲染
  // -------------
  const [age, setAge] = useState(42);             // 1. 读取变量名为 age 的 state(参数被忽略)
  const [fruit, setFruit] = useState('banana');   // 2. 读取变量名为 fruit 的 state(参数被忽略)
}

只要 Hook 的调用顺序在多次渲染之间保持一致,React 就能正确地将内部 state 和对应的 Hook 进行关联

注意:不要在循环、条件判断或者子函数中调用useState。只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。

如下代码:

let id = 0

function App () {
  let name,setName;
  let count,setCount;

  id += 1;
  if (id & 1) {
    // 奇数
    [count, setCount] = useState(0)
    [name, setName] = useState('张三')
  } else {
    // 偶数
    [name, setName] = useState('李四')
    [count, setCount] = useState(0)
  }

  return (
    <button type="button"
      onClick={() => {setCount(count + 1) }}
    >
      点击({count}), 名称 ({name})
    </button>
  )
}

这种方式在条件语句中使用 Hook 违反第一条规则,目前这种方式React会直接给与报错提示。

函数式更新

如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值。

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      数量: {count}
      <button onClick={() => setCount(initialCount)}>重置</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
    </>
  );
}

“+” 和 “-” 按钮采用函数式形式,因为被更新的 state 需要基于之前的 state。但是“重置”按钮则采用普通形式,因为它总是把 count 设置回初始值。

//class 组件
this.setState((prevState)=>({count:prevState.count+1}))

与 class 组件中的 setState 方法不同,useState 不会自动合并更新对象。你可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果。

惰性初始 state

defaultValue参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用:

//每次setInputValue引起的重新渲染进入函数组件内defaultValue都会被忽略。
const TestInput= (props) => {
  const defaultValue = props.value || '请输入'
  const [inputValue,setInputValue] = useState(defaultValue)
  return (
    <div>
      <input value={inputValue} onChange={(e)=>setInputValue(e.target.value)} /> {inputValue}
    </div>
  )
}
export default TestInput

//使用函数来解决每次渲染 state 需要通过复杂计算获得的问题
const TestInput= (props) => {
  const [inputValue,setInputValue] = useState(()=>{
    return props.value || '请输入'
  })
  return (
    <div>
      <input value={inputValue} onChange={(e)=>setInputValue(e.target.value)} /> {inputValue}
    </div>
  )
}
export default TestInput

文章作者: jackie chen
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 jackie chen !
评论
 上一篇
React新特性-Hooks之useEffect和useLayoutEffect(五) React新特性-Hooks之useEffect和useLayoutEffect(五)
Effect Hook 可以让你在函数组件中执行副作用操作,类似于class组件中的componentDidMount,componentDidUpdate和componentWillUnmount这三种生命周期函数的组合 系列目录 R
2019-07-21
下一篇 
React新特性-memo(三) React新特性-memo(三)
父组件进行了setState,众所周知子组件都会进行重新渲染。React.memo会做浅层对比传入的props,如果传入的props没有改变,这个子组件就不会重新渲染。 React.memo 为高阶组件。它与 React.PureComp
2019-07-11
  目录