一文全名搞懂react函数组件开发中的hook

一、hook的认识

Hookreact16.8的新增特性。它可以让你在不编写class的情况下使用state的特性,换句话说就是你使用函数组件也可以使用state等属性

  • 有了hook,你再也不用写class组件了,所有的组件都可以是函数组件
  • 有了hook,生命周期钩子函数可以丢一边了,函数组件中不需要考虑生命周期钩子函数

二、state的使用

  • state是状态属性,与之对应的是setState是设置状态属性的,一般情况都是成对的出现

  • 定义方式

    const [属性名称,set属性名称] = useState(初始化值);
    // 比如下面的定义
    const [age, setAge] = useState(10);
    const [books, setBooks] = useState(['西游记', '红楼梦']);
    const [address, setAddress] = useState({
      province: '广东省',
      city: '深圳市',
    });
    const [isLogin, setIsLogin] = useState(false);
    
  • 使用和修改状态值

    const addBook = () => {
      setBooks([...books, '水浒传']);
    }
    return ( 
      <>
        测试案例
        <div onClick={() => setAge(age+1)}>{age}</div>
        {
          books.map(item => (
            <li key={item}>{item}</li>
          ))
        }
        <button onClick={addBook}>添加书籍</button>
      </> 
    );
    

三、useEffect的使用

  • 1、基本的使用

    在页面加载完成后会执行一次,不管是修改ageisLogin的时候都会执行useEffect函数

    import { useState, useEffect } from 'react';
    
    const HookDemo = () => {
      const [age, setAge] = useState(10);
      const [isLogin, setIsLogin] = useState(false);
      useEffect(() => {
        console.log('useEffect执行了');
      });
    
      return (
        <>
          测试案例
          <div onClick={() => setAge(age + 1)}>{age}</div>
          <div onClick={() => setIsLogin(!isLogin)}>{isLogin}==</div>
        </>
      );
    }
    
    export default HookDemo;
    
  • 2、如果要修改只监听一个属性的时候才执行,需要添加第二个参数(第二个参数是一个数组,可以是监听多个属性)

    // 页面加载的时候会执行一次,当只改名age的时候会执行,改变isLogin的时候是不会执行的
    useEffect(() => {
      console.log('useEffect执行了');
    },[age]);
    
  • 3、在useEffect的回调函数中是不能使用async,如果一定要使用async的话,那么可以采用下面两种方式

    • 直接在useEffect中使用

      useEffect(() => {
        console.log('useEffect执行了');
        const demo = async () => {
          console.log('异步函数');
        }
        demo();
      },[age]);
      
    • 将异步函数直接提到useEffect外面来

      const demo = async () => {
        console.log('异步函数');
      };
      useEffect(() => {
        console.log('useEffect执行了');
        demo();
      },[age]);
      
  • 4、如果页面关闭的时候要触发事件就直接写在返回函数中就可以,比如关闭定时器,解绑dom事件

    useEffect(() => {
      console.log('useEffect执行了');
      return () => {
        console.log('关闭页面的时候执行');
      }
    },[age]);
    
    const handleGoHome = () => {
      history.push({
        pathname: '/user',
      });
    }
    

四、useLayoutEffect的使用

  • 基本上90%的时候都是用useEffect
  • useEffect是在render渲染结束后
  • useLayoutEffect是在DOM渲染后,如果你要操作DOM建议你在这个里面操作

五、useMemo的使用

useMemo其实就是对属性的缓存,当别的属性改变的时候改属性的不渲染,如果不使用的情况下就会一起渲染

  • 不使用useMemo的时候

    import { useState, useEffect, useMemo } from 'react';
    
    const HookDemo = () => {
      const [age, setAge] = useState(10);
      const [isLogin, setIsLogin] = useState(false);
      // 当点击isLogin改变的时候这个函数也会执行
      const noCacheAge = () => {
        console.log('age改变函数执行了');
        return age;
      }
    
      return (
        <>
          测试案例
          <div onClick={() => setAge(age + 1)}>{age}</div>
          <div onClick={() => setIsLogin(!isLogin)}>{isLogin}==</div>
          <div>{noCacheAge()}</div>
        </>
      );
    }
    
    export default HookDemo;
    
    
  • 使用useMemo改造上面的属性

    import { useState, useEffect, useMemo } from 'react';
    
    const HookDemo = () => {
      const [age, setAge] = useState(10);
      const [isLogin, setIsLogin] = useState(false);
    
      const noCacheAge = () => {
        console.log('age改变函数执行了');
        return age;
      }
    
      const cacheAge = useMemo(() => {
        console.log('缓存的函数属性改变');
        return age;
      }, [age]);
    
      return (
        <>
          测试案例
          <div onClick={() => setAge(age + 1)}>{age}</div>
          <div onClick={() => setIsLogin(!isLogin)}>{isLogin}==</div>
          <div>{noCacheAge()}</div>
          <div>{cacheAge}</div>
        </>
      );
    }
    
    export default HookDemo;
    

六、useCallback的使用

useCallback其实和useMemo类似,只是一个缓存属性一个缓存方法的,使用方式也类似

const handleAge = useCallback(() => {
  console.log('age');
  setAge(age + 1);
}, [age])

七、useReducer的使用

useReducer的使用与上面的hook来说相对复杂点,不是一行代码,一句话就可以解决的问题

  • 1、文件结构

    .
    ├── app.js
    ├── index.js
    ├── useContext.js
    └── user.js
    
    0 directories, 4 files
    
    • index.js入口组件
    • app.js渲染组件
    • user.js用户组件
    • useContext.js定义useReducer
  • 2、useReducer.js文件

    import React, { useReducer } from 'react';
    
    const initState = {
      isLogin: false,
      userInfo: {
        id: 1,
        name: '哈哈'
      }
    }
    
    const reducer = (state, action) => {
      switch(action.type) {
        case 'LOGIN':
          return {
            ...state,
            isLogin: action.payload,
          }
        default:
          return state;
      }
    }
    
    export const UserContext = React.createContext();
    
    export const UserContextProvider = (props) => {
      const [state, dispatch] = useReducer(reducer, initState);
      return (
        <UserContext.Provider value={{state, dispatch}}>
          {props.children}
        </UserContext.Provider>
      )
    }
    
  • 3、index.js文件内容

    import { UserContextProvider } from './useContext';
    import App from './app';
    
    const HookDemo = (props) => {
      return (
        <UserContextProvider>
          <App {...props}/>
        </UserContextProvider>
      );
    }
    
    export default HookDemo;
    
  • 4、app.js文件内容

    import { useContext } from 'react';
    import { UserContext } from './useContext';
    import User from './user';
    
    const App = () => {
      const {state, dispatch} = useContext(UserContext);
      console.log(state, '?');
      const handleLogin = () => {
        dispatch({
          type: 'LOGIN',
          payload: true,
        })
      }
      return (
        <div>
          {state.isLogin ? <User /> : <button onClick={handleLogin}>登录</button>}
        </div>
      );
    }
    
    export default App;
    
  • 5、user.js文件

    import { useContext } from 'react';
    import { UserContext } from './useContext';
    
    const User = () => {
      const {state} = useContext(UserContext);
      return (
        <div>
          用户组件
          <div> 名字: {state.userInfo.name} </div>
        </div>
      );
    }
    
    export default User;
    

八、自定义hook(修改页面标题的hook)

  • 1、在项目根目录中创建一个文件夹hooks

  • 2、创建一个文件hooks/useTitleHook.js

    import {useLayoutEffect, useState} from 'react';
    
    export const useTitleHook = (title) => {
      const [state, setState] = useState();
    
      useLayoutEffect(() => {
        document.title = title;
        setState(title);
      }, [title]);
      return state;
    }
    
  • 3、使用自定义的hooks

    const HookDemo = (props) => {
      const title = useTitleHook('测试页面');
      return (
        <UserContextProvider>
          <App {...props}/>
          <div>{title}</div>
        </UserContextProvider>
      );
    }
    
    export default HookDemo;
    

九、自定义hook(封装一个ajax的请求)

  • 1、在hooks文件夹下创建一个useHttpHook.js的文件

    import {useState, useEffect} from 'react';
    import {http} from '@/utils';
    
    export const useHttpHook = ({url, method='get', headers={}, body = {}, watch = []}) => {
      // 结果集
      const [result, setResult] = useState();
      // 是否已经请求完成
      const [loading, setLoading] = useState(true);
    
      useEffect(() => {
        http({url,method, headers, body, setResult, setLoading});
      },watch);
    
      return [result, loading];
    }
    
  • 2、关于utils/http方法的定义

    import { Toast } from 'antd-mobile';
    const baseUrl = '/api';
    
    export const http = ({
        url,
        method = 'get',
        headers = {},
        body = {},
        setLoading,
        setResult,
      }) => {
      setLoading && setLoading(true);
    
      const defaultHeader = {
        'Content-type': 'application/json'
      };
    
      let params;
      if (method.toUpperCase() === 'GET') {
        params = undefined;
      } else {
        params = {
          headers: {
            ...defaultHeader,
            headers
          },
          method,
          body: JSON.stringify(body)
        }
      }
    
      return new Promise((resolve,reject) => {
        fetch(baseUrl + url, params)
          .then(res => res.json())
          .then(res => {
            if (res.status === 200) {
              resolve(res.data);
              // 成功了将数据设置到result中
              setResult && setResult(res.data);
            } else {
              Toast.fail(res.errMsg);
              reject(res.errMsg);
            }
          }).catch(err => {
            Toast.fail(err);
            reject(err);
          }).finally(()=> {
            setLoading && setLoading(false);
          })
      })
    }
    
  • 3、使用自定义的hook请求数据

    const [houses, housesLoading] = useHttpHook({
      url: '/house/hot',
    });
    // 请求出来的结果在houses中,是否请求完成根据housesLoading来判断
    // 至于houses和housesLoading是自己随便定义的语义化名称
    

十、react项目中结合think-react-store的使用

  • 1、think-react-store官网地址

  • 2、基本的使用可以参考官方文档

  • 3、本人给出简单的使用方式加上标注

    • 基本的目录结构

      .
      ├── index.js
      ├── stores
      │   ├── index.js
      │   └── user.js
      └── user.js
      
      1 directory, 4 files
      
    • stores/user.js文件代码

      export default {
        // 状态
        state: {
          id: 123,
          name: 'cc'
        },
        // 同步方法
        reducers: {
          setName(state, payload) {
            return {
              ...state,
              ...payload
            }
          }
        },
        // 异步方法
        effects: {
          async setNameAsync(dispatch, rootState, payload) {
            await new Promise(resolve => {
              setTimeout(() => {
                resolve();
              }, 1000)
            })
            // 调用reducers里面的方法
            dispatch({
              type: 'setName', // type是指reducers里面的方法名,你想调用那个就用那个的方法名
              payload
            })
          }
        }
      }
      
    • stores/index.js文件代码

      export { default as user } from './user';
      
    • index.js代码(入口函数)

      import * as store from './stores';
      import {StoreProvider} from 'think-react-store';
      import User from './user';
      
      const StoreDemo = () => {
        return (
          <StoreProvider store={store}>
            <User/>
          </StoreProvider>
        );
      }
      
      export default StoreDemo;
      
    • user.js应用组件

      import { useStoreHook } from 'think-react-store';
      const User = (props) => {
        const {
          user: {
            id,
            name,
            setNameAsync,
            setName
          }
        } = useStoreHook();
        console.log(setNameAsync, '==>', name, id, setName);
      
        const handleChange1 = () => {
          setName({id: 10, name: '哈哈'});
        }
      
        const handleChange2 = () => {
          setTimeout(() => {
            setNameAsync({id: 100, name: '李四'})
          }, 1000);
        }
        return (
          <div>
            <div>用户ID: {id}</div>
            <div>用户名:{name}</div>
            <button onClick={handleChange1}>同步修改</button>
            <button onClick={handleChange2}>异步修改</button>
          </div>
        );
      }
      
      export default User;
      
  • 4、如果要配置中间件的话直接在入口中配置

    import * as store from './stores';
    import {StoreProvider} from 'think-react-store';
    import log from 'think-react-store/middlewares/log';
    import User from './user';
    
    const StoreDemo = () => {
      return (
        <StoreProvider store={store} middleware={[log]}>
          <User/>
        </StoreProvider>
      );
    }
    
    export default StoreDemo;
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水痕01

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值