React Hook是v16.8中新加入的特性,它的作用是让你可以在class外使用state和React的其他特性。可以将React Hook的作用理解为跨组件复用包含状态的逻辑,是继render-props和higher-order components之后的第三种状态共享方案,不会产生JSX嵌套地狱问题。

什么是钩子

钩子是一个方法,可以为函数式组件添加状态和生命周期特性,特别需要注意的是钩子不能在类中运行

  • 示例

运行代码

const Demo = ()=>{
    const [count,setCount] = useState(0)
    return (
        <div>
            <p>点击了{count}</p>
            <div onClick={()=>setCount(count+1)}>点击</div>
        </div>
    )
}

如上写了一个简单的Hook使用案例,这个案例里面首先使用const {useState} = React引入useState Hook(ES6语法为import {useState} from ‘react’,注意react版本需要在v16.8及以上),然后通过const [count,setCount] = useState(0)定义了变量count和setCount(修改变量count的值的方法),其中useState方法里面的参数是count的初始值,通过直接调用setCount修改变量值。

状态钩子state Hook

如上例子useState就是一个状态钩子,总结下来状态钩子的使用

  • 引入钩子

const {useState} = Reactimport {useState} from 'react'

  • 声明变量,可以是多个
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);

useState(initialValue)返回包含两个值的数组,第一个为state变量的初始值,第二个更新state变量的方法。

  • 更改状态值
function App() {
  const [obj, setObj] = useState({
    name: 'test',
    age: 18
  })
  return (
    <div className="App">
      <h1>{obj.name}</h1>
      <h2>{obj.age}</h2>
      <div onClick={()=>setObj({...obj, age: obj.age + 1})}>增加年龄</div>
    </div>
  );
}
ReactDOM.render(<App />, document.getElementById("root"));

如上代码,将多个状态放在一个对象中,然后通过setObj更新obj的值,从而实现状态的更新。

副作用钩子Effect Hook

在组件里面获取数据、订阅或手动更改DOM,我们称这些操作为副作用(他们可以影响其他组件且不能在渲染中完成)。副作用钩子useEffect的作用就是,为函数式组件带来执行副作用的能力。

如下示例,在React更新DOM之后设置文档的标题:

const {useState, useEffect} = React
const Demo = ()=>{
    const [count,setCount] = useState(0)
    useEffect(()=>{
        document.title = `点击了${count}次`
        return ()=>{//回收
        }
    })
    return (
        <div>
            <p>点击了{count}</p>
            <div onClick={()=>setCount(count+1)}>点击</div>
        </div>
    )
}

副作用函数是一个函数接受两个参数(Fun, Array)。调用useEffect就是告诉React在刷新DOM之后运行副作用函数(React默认在每一次渲染后运行副作用函数——包括第一次渲染),可以通过返回一个函数来指定如何回收它们。

副作用钩子的特性:

副作用钩子可调用多次

通过跳过执行副作用钩子优化性能

告诉React如果某些值没有发生改变,则跳过执行副作用钩子函数。如下代码,如果count没有发生改变则不会执行副作用钩子:

useEffect(()=>{
    document.title = '标题'
},[count])

如下代码,表示只执行一次,相当于生命周期方法componentDidMount,代码如下useEffect(()=>{},[])

useRef

useRef的作用和createRef的作用类似,都是用来生成对DOM对象或组件的引用。使用方法为[1]首先引入useRef,import {useRef} from 'react'[2]在dom节点上定义ref等于在函数内部声明一个变量let refDom=useRef()[3]调用当前Dom节点执行操作,如获取input焦点refDom.current.focus获取input值refDom.current.value。核心代码如下:

function App() {
  let refDom = useRef();
  const onClick = () => {
    refDom.current.focus();
  };
  return (
    <div className="App">
      <input ref={refDom} />
      <div onClick={onClick}>获取焦点</div>
    </div>
  );
}

useContext

接收一个context对象(React.createContext 的返回值)并返回该context的当前值。当前的context值由上层组件中距离当前组件最近的<MyContext.Provider>的value prop决定。当组件上层最近的<MyContext.Provider>更新时,该Hook会触发重渲染,并使用最新传递给MyContext provider的context value值。

useCallback

useCallback被称为记忆函数,主要作用是避免子组件的重复渲染useCallback接受内联回调函数及其依赖数组作为参数,它返回该回调函数的memoized版本,回调函数仅在某个依赖项发生改变时才会更新。

在类组件中,我们还可以通过this这个对象来存储函数,而在函数组件中没办法进行挂载了。所以函数组件在每次渲染的时候如果有传递函数的话都会重渲染子组件。

useMemo

useMemo的作用和useCallback类似,都是返回一个记忆函数,useCallback(fn, deps)等同于useCallback(()=>fn, deps),区别是useCallback不会执行第一个参数函数,而是将它返回给你,而useMemo会执行第一个函数并且将函数执行结果返回给你。

自定义Hook

自定义Hook的作用是,将组件逻辑提取到可重用的函数中。目前为止,我们常用的组件间共享逻辑的两种方法是render props或hight order component,而自定义hook是第三种。

自定义hook是以use开头的函数,在函数内部可以调用其他hook。

如下代码,将声明一个自定义hook:useFriendStatus,完整代码如下:

运行代码

const friendList = [
  { id: 1, name: 'Phoebe' },
  { id: 2, name: 'Rachel' },
  { id: 3, name: 'Ross' },
]
const useFriendStatus =(id)=>{
    const [isOnline, setOnline] = useState(null)
    useEffect(() => {
        setOnline(id===1 ? true : false)
    })
    return isOnline
}
const Demo = ()=>{
    const [currentId, setCurrentId] = useState(friendList[0].id)
    const isOnline = useFriendStatus(currentId)
    return (
        <div>
            <div>{isOnline ? "在线" : "离线"}</div>
            <select
                value={currentId}
                onChange={e=>setCurrentId(Number(e.target.value))}
            >
                {
                    friendList.map(friend=>(
                        <option key={friend.id} value={friend.id}>
                            {friend.name}
                        </option>
                    ))
                }
            </select>
        </div>
    )
}

使用Hook的注意要点

  • 只能在顶层调用Hooks

不能在循环、条件、嵌套函数里面调用Hooks,而只能在React函数的顶层调用。通过这样规定,确保在每次渲染组件的时候使用相同的顺序调用Hooks。

React依赖hooks被调用的顺序,因此React知道哪一个state对应哪一个useState。。如下当name存在会执行4个hooks,当name不存在时候则执行三个,这时React不知道第二个useState返回什么,因此会导致bug出现。

useState('Mary')
if(name!==''){
    useEffect(persistForm)
}
useState('Poppins')
useEffect(updateTitle)
  • 只能在React函数里面调用Hooks

  • Hook 函数必须以 “use” 命名开头,因为这样才方便 eslint 做检查

  • 设置Hook第二个参数为空数组,表示只监听一次

Hook使用案例

利用useRef处理capture value特性

capture value特性是指,存在延时的操作获取的数据不是最新的而是操作时候的数据。如下代码,点击按钮后立马修改输入框里面的值,三秒后显示的值是点击按钮时候输入框里面的值

function App() {
  let [message, setMessage] = useState("");
  const onChange = e => {
    setMessage(e.target.value);
  };
  const onClick = () => {
    setTimeout(() => {
      alert(`当前输入框的值是${message}`);
    }, 3000);
  };
  return (
    <button className="App">
      <input value={message} onChange={onChange} />
      <button onClick={onClick}>获取输入值</button>
    </div>
  );
}

为什么会出现这样的问题呢?这是因为hook的本质是函数式编程,props是直接作为参数传给函数的,立马更改输入框的值后props的值不会改变(已经断开和之前的联系,而class组件props是绑定在this上,更新this后props也会立即更新),解决办法是使用useRef,如下代码:

function App() {
  let [message, setMessage] = useState("");
  let mess = useRef();
  const onChange = e => {
    setMessage(e.target.value);
  };
  const onClick = () => {
    setTimeout(() => {
      alert(`当前输入框的值是${mess.current.value}`);
    }, 3000);
  };
  return (
    <div className="App">
      <input value={message} onChange={onChange} ref={mess} />
      <button onClick={onClick}>获取输入值</button>
    </div>
  );
}

点击按钮后立马更新输入框的值,三秒后,我们可以看到获取的是最新的值。

监听页面大小变化

运行代码

const getSize = ()=>{
    return {
        innerHeight: window.innerHeight,
        innerWidth: window.innerWidth,
        outerHeight: window.outerHeight,
        outerWidth: window.outerWidth 
    }
}
const Demo = ()=>{
    const [count,setCount] = useState(getSize())
    const handleResize = ()=>{
        setCount(getSize())
    }
    useEffect(()=>{
        window.addEventListener("resize", handleResize)
        return ()=>{
            window.removeEventListener("resize", handleResize)
        }
    })
    return (
        <div>
            <div>{count.innerHeight}</div>
            <div>{count.innerWidth}</div>
        </div>
    )
}

拿到组件 onChange 抛出的值

运行代码

const getInput = (initialValue)=>{
    const [value, setValue] = useState(initialValue)
    let onChange = useCallback((event)=>{
        setValue(event.currentTarget.value)
    },[])
    return {
        value,
        onChange
    }
}
const Demo = ()=>{
    const useInput = getInput('test')
    return (
        <div>
            <input {...useInput} />
            <div>{useInput.value}</div>
        </div>
    )
}

参考:

[1] Introducing Hooks

[2] 一篇看懂 React Hooks

[3] 精读《Function VS Class 组件》

Author:tenado
CeateTime:2019-04-15
Link:https://www.kelede.win/posts/React-Hook%E5%88%9D%E6%AD%A5%E7%90%86%E8%A7%A3/
License:本站博文无特别声明均为原创,转载请保留原文链接及作者
Previous:YAML语言介绍 Next:GitLab CI简单介绍