React-Hook基础使用与学习

204 阅读6分钟

Hook 的含义

  • 1.Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
  • 2.Hook 使你在无需修改组件结构的情况下复用状态逻辑。 这使得在组件间或社区内共享 Hook 变得更便捷
  • 3.React 默认提供的四个最常用的钩子。 useState() useContext() useReducer() useEffect()

useState()状态钩子

  • 1.useState()用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。
  • 2.这是一种在函数调用时保存变量的方式 —— useState 是一种新方法,它与 class 里面的 this.state 提供的功能完全相同。一般来说,在函数退出后变量就会”消失”,而 state 中的变量会被 React 保留。
声明 State
    	import React, { Suspense, useState, useEffect } from "react";
    	//sueState是接受状态的初始值的
    	//他默认有两个成员变量第一个是状态的初始值----第二个是修改初始值状态的函数,这个函数必须以set开头的
    	const [buttonText, setButtonText] = useState("1");
读取 State
    	当我们想在 class 中显示当前的 count,我们读取 this.state.buttonText:
        	<p>You clicked {this.state.buttonText} times</p>
        	在函数中,我们可以直接用 count:
        	<p>You clicked {buttonText} times</p>
更新 Stateclass 中,我们需要调用 this.setButtonText() 来更新 buttonText 值:
     	<button onClick={() => this.setButtonText({ count: this.state.buttonText + 1 })}>Click me</button>
    	在函数中,我们已经有了 setCount 和 count 变量,所以我们不需要 this:
    	<button onClick={() => setButtonText(buttonText + 1)}>Click me</button>

useContext():共享状态钩子

  • 如果需要在组件之间共享状态,可以使用useContext()。
如果使用数据共享的话必须在声明一个公共的AppContext---AppContext = React.createContext({})
必须使用AppContext.Provider标签包裹住,
value={{username:buttonText}} value声明的值就是传递的值
<AppContext.Provider value={{username:buttonText}}>
    <div className="App">
      <Navbar />
      <Messages />
    </div>
  </AppContext.Provider>;

设置共享状态

import React  from "react";
export const AppContext = React.createContext({})

Navbar代码

import React, { useState,useContext } from "react";
import {AppContext} from "./AppContext";
export default function Navbar(){
    const [Navbar,steNavbar] = useState("我是Navbar")
    const { username } = useContext(AppContext);
    function handleClick(){
        return steNavbar('我是Navbar222222')
    }
    return <dev className="Navbar">
 <button  onClick={handleClick}>{Navbar}我是传递下来的----{username}</button>
    </dev> 
}

Messages代码

import React, { useState ,useContext} from "react";
import {AppContext} from "./AppContext";
export default function Messages(){
    const { username } = useContext(AppContext);
    const [Messages,steMessages] = useState("我是Messages")
    function handleClick(){
        return steMessages('我是Messages222222')
    }
return <dev className="Messages">
    <button  onClick={handleClick}>{Messages}我是传递下来的----{username}</button>
</dev>


}

useReducer():action 钩子

  • useReducer是针对于复杂的state操作逻辑,嵌套的state的对象,推荐使用useReducer
  • 第一个参数:reducer函数
  • 第二个参数:初始化的state。按照官方的说法:对于复杂的state操作逻辑,嵌套的state的对象,推荐使用useReducer。

声明useReducer

使用useState版login实现--如果使用他实现在login函数中当登录成功、失败时进行了一系列复杂的state设置。可以想象随着需求越来越复杂更多的state加入到页面,更多的setState分散在各处,很容易设置错误或者遗漏,维护这样的老代码更是一个噩梦

   function LoginPage() {
        const [name, setName] = useState(''); // 用户名
        const [pwd, setPwd] = useState(''); // 密码
        const [isLoading, setIsLoading] = useState(false); // 是否展示loading,发送请求中
        const [error, setError] = useState(''); // 错误信息
        const [isLoggedIn, setIsLoggedIn] = useState(false); // 是否登录
        const login = (event) => {
            event.preventDefault();
            setError('');
            setIsLoading(true);
            login({ name, pwd })
                .then(() => {
                    setIsLoggedIn(true);
                    setIsLoading(false);
                })
                .catch((error) => {
                    // 登录失败: 显示错误信息、清空输入框用户名、密码、清除loading标识
                    setError(error.message);
                    setName('');
                    setPwd('');
                    setIsLoading(false);
                });
        }
        return ( 
            //  返回页面JSX Element
        )
    }

useReducer版login

const initState = {
        name: '',
        pwd: '',
        isLoading: false,
        error: '',
        isLoggedIn: false,
    }
    function loginReducer(state, action) {
        switch(action.type) {
            case 'login':
                return {
                    ...state,
                    isLoading: true,
                    error: '',
                }
            case 'success':
                return {
                    ...state,
                    isLoggedIn: true,
                    isLoading: false,
                }
            case 'error':
                return {
                    ...state,
                    error: action.payload.error,
                    name: '',
                    pwd: '',
                    isLoading: false,
                }
            default: 
                return state;
        }
    }
    function LoginPage() {
        const [state, dispatch] = useReducer(loginReducer, initState);
        const { name, pwd, isLoading, error, isLoggedIn } = state;
        const login = (event) => {
            event.preventDefault();
            dispatch({ type: 'login' });
            login({ name, pwd })
                .then(() => {
                    dispatch({ type: 'success' });
                })
                .catch((error) => {
                    dispatch({
                        type: 'error'
                        payload: { error: error.message }
                    });
                });
        }
        return ( 
            //  返回页面JSX Element
        )
    }

可以看到login函数现在更清晰的表达了用户的意图,开始登录login、登录success、登录error。LoginPage不需要关心如何处理这几种行为,那是loginReducer需要关心的,表现和业务分离。

useEffect():副作用钩子

  • useEffect()用来引入具有副作用的操作,最常见的就是向服务器请求数据。以前,放在componentDidMount里面的代码,现在可以放在useEffect()。
  • useEffect()接受两个参数。第一个参数是一个函数,异步操作的代码放在里面。
  • 第二个参数是一个数组,用于给出 Effect 的依赖项,只要这个数组发生变化,useEffect()就会执行
  • 第二个参数可以省略,这时每次组件渲染时,就会执行useEffect()。
  useEffect(()  =>  {
    // Async Action
    // alert(22222)
  }, [buttonText])

注意(useEffect需要清除的的数据)

useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // 清除订阅
    subscription.unsubscribe();
  };
});
  • 什么都不传,组件每次 render 之后 useEffect 都会调用,相当于 componentDidMount 和 componentDidUpdate。
  • 传入一个空数组 [], 只会调用一次,相当于 componentDidMount 和 componentWillUnmount。
  • 传入一个数组,其中包括变量,只有这些变量变动时,useEffect 才会执行
  • 我们研究了如何使用不需要清除的副作用,还有一些副作用是需要清除的。例如订阅外部数据源。这种情况下,清除工作是非常重要的,可以防止引起内存泄露!
  function App () {
    const [ count, setCount ] = useState(0)
    const [ width, setWidth ] = useState(document.body.clientWidth)
    const onChange = () => {
      setWidth(document.body.clientWidth)
    }
    useEffect(() => {
      // 相当于 componentDidMount
      console.log('add resize event')
      window.addEventListener('resize', onChange, false)
      return () => {
        // 相当于 componentWillUnmount
        window.removeEventListener('resize', onChange, false)
      }
    }, [])
    useEffect(() => {
      // 相当于 componentDidUpdate
      document.title = count
    })
    useEffect(() => {
      //每次点击都会调用
      console.log(`count change: count is ${count}`)
    }, [ count ])
    return (
      <div>
        名称: { count } 
        宽度: { width }
        <button onClick={() => { setCount(count + 1)}}>点我</button>
      </div>
      )
  }

第一个 useEffect 中的 ‘add resize event’ 只会在第一次运行时输出一次,无论组件怎么 render,都不会在输出,第二个 useEffect 会在每次组件 render 之后都执行,title 每次点击都会改变, 第三个 useEffect, 只要有在第一次运行和 count 改变时,才会执行,屏幕发生改变引起的 render 并不会影响第三个 useEffect。

useRef()

  • useRef 返回一个可变的 ref 对象
  • 可以保存任何类型的值:dom、对象等任何可变化的值
  • ref对象与自己一个对象的区别是:useRef会在每次渲染时返回同一个ref对象,即返回的ref对象在组件的整个生命周期内保持不变。自己创建对象每次渲染时都建立一个新的。
  • ref对象的值发生改变之后,不会触发组件重新渲染。 例子:
import React, {
        useRef,
        useEffect,
} from 'react'
import Child from'./Child'
const Dome = () => {
        const domRef = useRef<any>(null);
        const childRef = useRef<any>(null)
        useEffect(() => {
                console.log("ref:deom-init", domRef, domRef.current);
                console.log("ref:child-init", childRef, childRef.current);
        });
        const showChild = () => {
                console.log("ref:child", childRef, childRef.current); 
                childRef.current.say()
        };
        return (
                <div style={{ margin: "100px", border: "2px dashed", padding: "20px",background:'red' }}>
                        <h2>这是外层组件</h2>
                        <div
                                onClick={() => {
                                        console.log("ref:deom", domRef, domRef.current);
                                        domRef.current.value = 'hh';
                                }}
                        >
                                <label>我是一个输入框</label><input ref={domRef} />
                        </div>
                        <br />
                        <p onClick={showChild} style={{ marginTop: "20px" }}>
                                这是子组件
                        </p>
                        <div style={{ border: "1px solid", padding: "10px" }}>
                                <Child ref={childRef} />
                        </div>
                </div>
        )
}

export default Dome

子组件

import React, {
        useImperativeHandle,
        forwardRef,
} from 'react'
const ChildComponent = (props, ref) => {
        useImperativeHandle(ref, () => ({
          say: sayHello,
          name:'22222'
        }));
        const sayHello = () => {
          alert("hello,我是子组件");
        };
        return <h3>子组件</h3>;
      };
     
export default forwardRef(ChildComponent)
  • useImperativeHandle(ref,createHandle,[deps])可以自定义暴露给父组件的实例值。如果不使用,父组件的ref(chidlRef)访问不到任何值(childRef.current==null)
  • useImperativeHandle应该与forwradRef搭配使用
  • React.forwardRef会创建一个React组件,这个组件能够将其接受的ref属性转发到其组件树下的另一个组件中。
  • React.forward接受渲染函数作为参数,React将使用prop和ref作为参数来调用此函数。

参考链接

react.docschina.org/docs/hooks-… www.ruanyifeng.com/blog/2019/0… blog.csdn.net/adley_app/a… blog.csdn.net/weixin_4372…