关于javascript:来聊一下做项目时用到的React-Hook

27次阅读

共计 7188 个字符,预计需要花费 18 分钟才能阅读完成。

一、React Hook 是什么

这是官网的解释:_Hook_ 是 React 16.8 的新增个性。它能够让你在不编写 class 的状况下应用 state 以及其余的 React 个性。

阮一峰大神的解释:React Hooks 的意思是,组件尽量写成纯函数,如果须要内部性能和副作用,就用钩子把内部代码 ” 钩 ” 进来。

我的了解:Hook 是能够提供给咱们更加简洁的形式去应用 React 的其余个性。

二、React Hook 的性质

  • 齐全可选的。 你无需重写任何已有代码就能够在一些组件中尝试 Hook。然而如果你不想,你不用当初就去学习或应用 Hook。
  • 100% 向后兼容的。 Hook 不蕴含任何破坏性改变。
  • 当初可用。 Hook 已公布于 v16.8.0。

三、为什么要应用 React Hook

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

    能够应用 Hook 从组件中提取状态逻辑,使得这些逻辑能够独自测试并复用。Hook 使你在无需批改组件构造的状况下复用状态逻辑。 这使得在组件间或社区内共享 Hook 变得更便捷。

  • 简单组件变得难以了解

    咱们常常保护一些组件,组件起初很简略,然而逐步会被状态逻辑和副作用充斥。为了解决这个问题,Hook 将组件中互相关联的局部拆分成更小的函数(比方设置订阅或申请数据),而并非强制依照生命周期划分。你还能够应用 reducer 来治理组件的外部状态,使其更加可预测。

  • 难以了解的 class

    Hook 使你在非 class 的状况下能够应用更多的 React 个性。 从概念上讲,React 组件始终更像是函数。而 Hook 则拥抱了函数,同时也没有就义 React 的精力准则。Hook 提供了问题的解决方案,无需学习简单的函数式或响应式编程技术。

四、目前遇到的 Hook

1.useState

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

返回值:state,更新 state 的函数 setState

setState 函数用于 更新 state。它接管一个新的 state 值并将组件的一次从新渲染退出队列。setState(newState);

React 会确保 setState 函数的标识是稳固的,并且不会在组件从新渲染时发生变化。

参数:初始的 state:initialState

在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值雷同。

示例代码:

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

留神:与 class 组件中的 setState 办法不同,useState 不会主动合并更新对象。你能够用函数式的 setState 联合开展运算符来达到合并更新对象的成果。

setState(prevState => {
  // 也能够应用 Object.assign
  return {...prevState, ...updatedValues};
});

2.useEffect

该 Hook接管一个蕴含命令式、且有可能有副作用代码的函数

useEffect(didUpdate);        //didUpdate 指的是蕴含命令式、且有可能有副作用代码的函数

在函数组件主体内(这里指在 React 渲染阶段)扭转 DOM、增加订阅、设置定时器、记录日志以及执行其余蕴含副作用的操作都是不被容许的,因为这可能会产生莫名其妙的 bug 并毁坏 UI 的一致性。

应用 useEffect 实现副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。你能够把 effect 看作从 React 的纯函数式世界通往命令式世界的逃生通道。

默认状况下,effect 将在每轮渲染完结后执行,但你能够抉择让它 在只有某些值扭转的时候 才执行。

[](https://zh-hans.reactjs.org/d…,组件卸载时须要革除 effect 创立的诸如订阅或计时器 ID 等资源。要实现这一点,useEffect 函数需返回一个革除函数。以下就是一个创立订阅的例子:

useEffect(() => {const subscription = props.source.subscribe();
  return () => {
    // 革除订阅
    subscription.unsubscribe();};
});

默认状况下,effect 会在每轮组件渲染实现后执行 。这样的话, 一旦 effect 的依赖发生变化,它就会被从新创立

然而,在某些场景下这么做可能会矫枉过正。比方,在下面的例子中,咱们不须要在每次组件更新时都创立新的订阅,而是仅须要在 source prop 扭转时从新创立。

要实现这一点,能够给 useEffect 传递第二个参数,它是 effect 所依赖的值数组。更新后的示例如下:

useEffect(() => {const subscription = props.source.subscribe();
    return () => {subscription.unsubscribe();
    };
  },
  [props.source],            // 第二个参数是 Effect 的依赖项,在这里,只有当该数组发生变化的时候,才去执行 useEffect
);

此时,只有 当 props.source 扭转后才会从新创立订阅

3.useContext

const value = useContext(MyContext);    //MYContext 是创立好的 context 对象
  • 接管一个 context 对象 (React.createContext 的返回值)并 返回该 context 的以后值。以后的 context 值由下层组件中距离以后组件最近的 <MyContext.Provider> 的 value prop 决定。
  • 当组件下层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并应用最新传递给 MyContext provider 的 context value 值。即便先人应用 React.memo 或 shouldComponentUpdate,也会在组件自身应用 useContext 时从新渲染。
  • 调用了 useContext 的组件总会在 context 值变动时从新渲染。
  • useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者 <MyContext.Consumer>

留神:

1.useContext(MyContext) 只是让你可能_读取_ context 的值以及订阅 context 的变动。你依然须要在下层组件树中应用 <MyContext.Provider> 来为上层组件_提供_ context。

示例代码:

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (<ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {const theme = useContext(ThemeContext);        // 这里订阅到了 provider 传递下来的 value 值
  return (<button style={{ background: theme.background, color: theme.foreground}}>
      I am styled by theme context!
    </button>
  );
}

4.useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init);

它接管一个形如 (state, action) => newState 的 reducer,并返回以后的 state 以及与其配套的 dispatch 办法。

useReducer 和 useState 的比拟:这两者都是返回了新的状态和一个函数。在某些场景下,useReducer 会比 useState 更实用,例如 state 逻辑较简单且蕴含多个子值,或者下一个 state 依赖于之前的 state 等。并且,应用 useReducer 还能给那些会触发深更新的组件做性能优化。

以下是用 reducer 重写 useState 一节的计数器示例:

const initialState = {count: 0};

function reducer(state, action) {            //reducer
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();}
}

function Counter() {const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

第三个参数:init

你能够抉择 惰性地创立初始 state。为此,须要 将 init 函数作为 useReducer 的第三个参数传入,这样初始 state 将被设置为 init(initialArg)。

还是上一个计数器的代码,这里应用了第三个参数增加了一个 reset 性能:

function init(initialCount) {            //init 函数:用于惰性创立初始 state
  return {count: initialCount};
}

function reducer(state, action) {switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    case 'reset':
      return init(action.payload);
    default:
      throw new Error();}
}

function Counter({initialCount}) {const [state, dispatch] = useReducer(reducer, initialCount, init);            //init 函数作为第三个参数传入 useReducer
  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>
        Reset
      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

5.useRef

const refContainer = useRef(initialValue);

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内放弃不变。

一个常见的用例便是命令式地拜访子组件:

function TextInputWithFocusButton() {const inputEl = useRef(null);        // 创立一个 ref 对象
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输出元素
    inputEl.current.focus();};
  return (
    <>
      <input ref={inputEl} type="text" />            // 将创立好的 ref 对象放进 ref 属性中
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

useRef 和 ref 属性的区别:

  • 相同点:以咱们之前对 refs 的理解,如果将 ref 对象以 <div ref={myRef} /> 模式传入组件,则无论该节点如何扭转,React 都会将 ref 对象的 .current 属性设置为相应的 DOM 节点
  • 不同点:然而,useRef() 比 ref 属性更有用。它能够很不便地保留任何可变值,其相似于在 class 中应用实例字段的形式。这是因为 它创立的是一个一般 Javascript 对象。而 useRef() 和自建一个 {current: …} 对象的惟一区别是,useRef 会在每次渲染时返回同一个 ref 对象。

留神:

ref 对象内容发生变化时 useRef 并_ 不会 _ 告诉你。变更 .current 属性不会引发组件从新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则须要应用回调 ref 来实现。

6.useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  • 参数:
  1. 一个函数,执行一些简单的计算操作
  2. 一个数组,是第一个参数执行的依赖值组成的数组
  • 返回值:一个 memoized 值
  • 留神点:

    • 把“创立”函数和依赖项数组作为参数传入 useMemo,它仅会 在某个依赖项扭转时才从新计算 memoized 值 。这种优化有助于 防止在每次渲染时都进行高开销的计算
    • 记住,传入 useMemo 的函数会在 渲染期间执行(useEffect 是在渲染之后才执行)请不要在这个函数外部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的实用领域,而不是 useMemo。
    • 如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。
    • 你能够把 useMemo 作为性能优化的伎俩,但不要把它当成语义上的保障。未来,React 可能会抉择“忘记”以前的一些 memoized 值,并在下次渲染时从新计算它们,比方为离屏组件开释内存。先编写在没有 useMemo 的状况下也能够执行的代码 —— 之后再在你的代码中增加 useMemo,以达到优化性能的目标。
    • 依赖项数组不会作为参数传给“创立”函数。尽管从概念上来说它体现为:所有“创立”函数中援用的值都应该呈现在依赖项数组中。将来编译器会更加智能,届时主动创立数组将成为可能。
  • 与 useEffect 的区别:

    • useMemo 是在组件渲染的时候执行,而 useEffect 是在组件渲染完之后提早执行
    • 不要在传入 useMemo 的函数中执行与渲染无关的操作,诸如副作用之类的操作属于 useEffect 的适用范围,而不是 useMemo

补充:副作用

在计算机科学中,函数副作用指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。例如批改全局变量(函数外的变量)或批改参数。

对于运行在浏览器的 JS 来说,副作用包含且不限于:

  • console.log()
  • 操作 DOM
  • http 申请
  • 批改全局变量

五、创立本人的 Hook

上例的 Hooks 代码还能够封装起来,变成一个自定义的 Hook,便于共享。

const usePerson = (personId) => {const [loading, setLoading] = useState(true);
  const [person, setPerson] = useState({});
  useEffect(() => {setLoading(true);
    fetch(`https://swapi.co/api/people/${personId}/`)
      .then(response => response.json())
      .then(data => {setPerson(data);
        setLoading(false);
      });
  }, [personId]);  
  return [loading, person];
};

下面代码中,usePerson()就是一个自定义的 Hook。

Person 组件就改用这个新的钩子,引入封装的逻辑。

const Person = ({personId}) => {const [loading, person] = usePerson(personId);

  if (loading === true) {return <p>Loading ...</p>;}

  return (
    <div>
      <p>You're viewing: {person.name}</p>
      <p>Height: {person.height}</p>
      <p>Mass: {person.mass}</p>
    </div>
  );
};

本文未完继续更新中 …

正文完
 0