6.React Hook 概述(开发中遇到的问题与解决)


这里的所有内容均摘自网上优秀的文章,或再加工,只供自己学习用,如有侵权,请联系。会在以后的使用过程中不断补充,修改。

React Hook 概述

什么是 Hook:

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

这篇文章有什么:

  这里不过多阐述使用 Hook 的动机,网上都有,如果一定要用 Hook ,这片文章将收集,初次使用 Hook ,所需要知道的干货。

Hook 知识点:

  State Hook | Effect Hook | useContext | useReducer | useCallback | useMemo | useRef | useHistory

 

State Hook

import React, { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

 const [state, setState] = useState(initialState); 

useState:主要是为了给函数添加状态的。最好(必须)在函数的最外层申明使用,不要在if或者else里面使用。

initialState:是 state 的初始值,不限制数据类型,不写默认 undefined。

setState:是改变 state 的方法,类似于原来的 setState({state:false }),区别是没有钩子函数,也就是不能这样- ->  setState({state:false }, () => { console.log(state) })  操作。 setState 函数用于更新 state,它接收一个新的 state 值并将组件的一次重新渲染加入队列(注意:可以在 render 中使用 setState ,不会重复触发 render)。

在后续的重新渲染中,useState 返回的第一个值将始终是更新后最新的 state。

 

Effect Hook

useEffect的作用是依赖变化的时候,执行函数(第一个参数),其中第二个参数为依赖。

  第二个参数的作用:

    虽然 useEffect 会在浏览器绘制后延迟执行,但会保证在任何新的渲染前执行。React 将在组件更新前刷新上一轮渲染的 effect。

    不写“依赖”,可能会导致没必要的刷新,甚至无限刷新。

    写上第二个参数,effect 会监测“依赖”是否变化,当“依赖”变化时才会刷新。

    若依赖是一个空数组,effect 会判定依赖没有更新,所以只会执行一次。

  effect 第一次被执行的生命周期:第一次 render(创建 DOM),执行 effect ,第二次 render,...

例子1:

function App() {
  const [width,setWidth] = useState(window.innerWidth)
useEffect(()
=>{ const handleResize = () => setWidth(window.innerWidth) window.addEventListener('resize',handleResize) return ()=>{ // 清除订阅、定时器、等 window.removeEventListener('resize',handleResize) } }, [])
return ( <div> <p>{width}</p> </div> ) }

 

useContext

之前我们使用context (上下文)来解决多层嵌套传props,分三步

  1. createContext创建Context
  2. 使用Context.Provider组件提供数据
  3. Context.Provider的所有后代组件,都可以通过Context.Consumer使用数据数据
const ColorContext = React.createContext('black')

function Button(props){
    return (
      <ColorContext.Consumer>
      {color=>
        <button style={{ background: color }}>
          {color,props.children}
        </button>}
      </ColorContext.Consumer>
    );
}

function MiddleWare(){
  return (
    <Button>我来了</Button>
  )
}

function App() {
  return (
    <div>
      <ColorContext.Provider value='yellow'> 
        <MiddleWare></MiddleWare>
      </ColorContext.Provider>
    </div>
  );
}

useContext 例子:

如果需要在组件之间共享状态(比如两个有共同父级的子组件之间),可以使用useContext()。

一层的数据传递,感觉没有必用 useContext,如果是多层嵌套的话,父传子的方式就显得很不好了,此时应该用到它。

需要用到状态的子组件:

import React, { useContext } from "react";
function
Button2(props){ const color = useContext(ColorContext) return <button style={{ background: color }}>{(color, props.children)}</button> }

提供状态的父组件

import React from "react";
const TestContext= React.createContext({});
function
MiddleWare(props) { return <ColorContext.Provider value="yellow"> <Button2>很多个子组件的其中一个</Button2> </ColorContext.Provider> }

 

useReducer

让我们来回忆一下 使用redux使用reducer

//1.首先创建一个store index.store.js
export default function configStore(){
    const store = createStore(rootReducer,applyMiddleware(...middlewares))
    return store
}

//2.引入store app.js
  render() {
    return (
      <Provider store={store}>
        <Index />
      </Provider>
    )
  }

//3.定义action和创建reducder index.action.js index.reducer.js
export const ADD = 'ADD'
export const DELETE = 'DELETE'
function todos(state = INITAL_STATE, action) {
  switch action.type{
    case ADD:{...}
    case DELETE:{...}
  }
}

//4.页面中使用reducer  component.js
export default connect(mapStateToProps, mapDispatchToProps)(Component);

太复杂了有没有,(使用dva可以简化写法)
而使用useReducer可以省略很多代码:

//index.js
  const { state, dispatch } = useContext(reducerContext);
  return (
    <div className="App">
      <>
        Count: {state.count}
        <button
          onClick={() => dispatch({ type: "reset", payload: { count: 0 } })}
        >Reset</button>
        <button onClick={() => dispatch({ type: "increment" })}>+</button>
        <button onClick={() => dispatch({ type: "decrement" })}>-</button>
        <reducerContext.Provider value={{ state, dispatch }}>
          <ChangeCount />
        </reducerContext.Provider>
      </>
    </div>
  );
不过 useReducer 不支持共享数据,推荐使用 redux-react-hook ,同样是通过 context 实现的 redux
但是不知是否有像查看 store 的浏览器插件,或者 redux-logger 这样的中间件帮助我们查看状态的变化,redux 的生态还是更好一点的
 

useCallback

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

返回一个 memoized 回调函数。

把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。

useCallback(fn, deps) 相当于  useMemo(() => fn, deps)

注意:

依赖项数组不会作为参数传给回调函数。虽然从概念上来说它表现为:所有回调函数中引用的值都应该出现在依赖项数组中。未来编译器会更加智能,届时自动创建数组将成为可能。

我们推荐启用 eslint-plugin-react-hooks 中的 exhaustive-deps 规则。此规则会在添加错误依赖时发出警告并给出修复建议。

 

useMemo

 工作中的经验:memo 可以提高性能,没有上网查过具体信息。
使用:
import React, {memo, useMemo} from "react";

//...不清楚 useMemo 怎么用,以后补充

export default memo(xxxx) //导出的时候包一下

 

useRef

写一个 useOutsideClick 函数

function useOutsideClick(ref, fnc) {
  useEffect(() => { const handleClickOutside = e => { if (e.target !== ref.current && !ref.current.contains(e.target)) { fnc(); } }; window.addEventListener("click", handleClickOutside); return () => { window.removeEventListener("click", handleClickOutside); }; }, [ref, fnc]); }

使用

function App() {
  const ref = useRef(null); useOutsideClick(ref, () => { console.log('OUTSIDE CLICKED'); }); return ( <div ref={ref} style={{ width: 200, height: 200, background: 'red', }} />  ); }

 

useHistory

react.js-Hooks 路由跳转
useHistory 钩子允许您访问可能用于导航的历史实例。
import { useHistory } from "react-router-dom";

function HomeButton() {
  let history = useHistory();

  function handleClick() {
    history.push("/home");
  }

  return (
    <button type="button" onClick={handleClick}>
      Go home
    </button>
  );
}

 

开发中遇到的问题与解决

错误代码:
import BtnGroup from "componten/BtnGroup/BtnGroup";

function Father() {
  const [defaultValue] = useState([
    {name: "全部", key: 0, isAll: true, disabled: false},
    {name: "中国人", key: 1, disabled: false},
    {name: "米国人", key: 2, disabled: false}
  ]);
  const click = () => {
    //...
  }
  return (
    <div>
      <BtnGroup>
        defaultValue={[defaultValue[0]]}
        handleClick={click}
      </BtnGroup>
    </div>
  );
}

export default memo(Father)

// 下面是子组件的部分代码,我在子组件中使用 useEffect 监测 props.defaultValue
useEffect(() => {
  console.log("在这个做一些事,设置组件的初始状态")
},[props.defaultValue])

细心的朋友可能发现了,在 Father 组件每次触发 render 的时候,都会触发子组件的 useEffect ,应为组件的 useEffect 监测了 props.defaultValue,

这时,又有人问了,我给子组件传的 defaultValue 是不会改变的,如何触发了 子组件的 useEffect ?

注意:我给子组件传 defaultValue[0] 的时候,在外面有包了一层 [ ] , 结果是--> [defaultValue[0]] ,也就是说,每次触发 Father 的 render,

      子组件的 defaultValue 都会拿到一个新地址的 [ ],同理,只要在 function Father() { } 的函数体内定义的 [ ] (对象) 的指针,都在状态改变的时候指向新的作用域,

      从而被 子组件的 useEffect 监测到,触发不必要的刷新。

解决:1. 不要监测 props.defaultValue,给一个[ ](空数组)。

   2. 在函数 function Father() { } 体外申明一个变量 defaultValue = [{name: "全部", key: 0, isAll: true, disabled: false}]

   3. 在函数 function Father() { } 体内用 useState 申明变量 const [defaultValue] = useState([{name: "全部", key: 0, isAll: true, disabled: false}])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

作者:趣谈前端_徐小夕
链接:https://www.jianshu.com/p/16bef85ebd30
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 
作者:春饼sama
链接:https://www.jianshu.com/p/7bc5eb7eaaac
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
useHistory


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM