文件结构
- App.js
// import StateHook from './components/StateHook';
// import EffectHook from './components/EffectHook';
// import LayoutEffectHook from './components/LayoutEffectHook';
// import MemoHook from './components/MemoHook';
// import CallbackHook from './components/CallbackHook';
// import ReactMemo from './components/ReactMemo';
// import RefHook from './components/RefHook';
// import ForwardRefHook from './components/ForwardRefHook';
// import ImperativeHandleHook from './components/ImperativeHandleHook';
// import ContextHook from "./components/ContextHook";
// import ReducerHook from "./components/ReducerHook";
// import ObjectDemo from "./components/ObjectDemo";
const App = () => {
return (
<div>
{/* <StateHook></StateHook> */}
{/* <EffectHook></EffectHook> */}
{/* <LayoutEffectHook></LayoutEffectHook> */}
{/* <MemoHook></MemoHook> */}
{/* <CallbackHook></CallbackHook> */}
{/* <ReactMemo></ReactMemo> */}
{/* <RefHook></RefHook> */}
{/* <ForwardRefHook></ForwardRefHook> */}
{/* <ImperativeHandleHook></ImperativeHandleHook> */}
{/* <ContextHook></ContextHook> */}
{/* <ReducerHook></ReducerHook> */}
{/* <ObjectDemo></ObjectDemo> */}
</div>
);
};
export default App;
- index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App />
);
useState用法
介绍
useState 可以使函数组件像类组件一样拥有 state,函数组件通过 useState 可以让组件重新渲染,更新视图。
useState 基础介绍:
const [ state , dispatch ] = useState(initData)
- state,目的提供给 UI ,作为渲染视图的数据源。
- dispatchAction 改变 state 的函数,可以理解为推动函数组件渲染的渲染函数。
- initData 有两种情况,第一种情况是非函数,将作为 state 初始化的值。 第二种情况是函数,函数的返回值作为 useState 初始化的值。
注意事项
- 在函数组件一次执行上下文中,state 的值是固定不变的。
- 如果两次 dispatchAction 传入相同的 state 值,那么组件就不会更新。
- 当触发 dispatchAction 在当前执行上下文中获取不到最新的 state, 只有再下一次组件 rerender 中才能获取到。
使用
import { useState } from "react";
const StateHook = () => {
let [n, setN] = useState(0);
const handleClick = () => {
setN(cur => cur + 1);
};
return (
<div>
<h1>State Hook</h1>
<h2>{n}</h2>
<button onClick={handleClick}>Increment</button>
</div>
);
};
export default StateHook;
useEffect用法
介绍
useEffect 第一个参数 callback, 返回的 destory , destory 作为下一次callback执行之前调用,用于清除上一次 callback 产生的副作用。
第二个参数作为依赖项,是一个数组,可以有多个依赖项,依赖项改变,执行上一次callback 返回的 destory ,和执行新的 effect 第一个参数 callback 。
对于 useEffect 执行, React 处理逻辑是采用异步调用 ,对于每一个 effect 的 callback, React 会向 setTimeout回调函数一样,放入任务队列,等到主线程任务完成,DOM 更新,js 执行完成,视图绘制完毕,才执行。所以 effect 回调函数不会阻塞浏览器绘制视图。
常用功能:
- 请求数据。
- 操作 dom , 在 React Native 中可以通过 ref 获取元素位置信息等内容。
- 注册事件监听器, 事件绑定,在 React Native 中可以注册 NativeEventEmitter 。
- 还可以清除定时器,延时器,解绑事件监听器等。
使用
import { useEffect,useState } from "react";
const EffectHook = () => {
const [count,setCount] = useState(0);
const [age,setAge] = useState(18);
useEffect(() => {
console.log(`useEffect--${count}`);
return () => {
console.log("useEffect return");
}
}, [count]);
useEffect(() => {
console.log(`useEffectAge--${age}`);
return () => {
console.log("useEffect returnAge");
}
}, []);
return (
<div>
<h1>Effect Hook</h1>
<h2>组件{count}</h2>
<h2>年龄{age}</h2>
<button onClick={() => setCount(count + 1)}>count+</button>
<button onClick={() => setAge(age + 1)}>age+</button>
</div>
);
}
export default EffectHook;
useLayoutEffect用法
介绍
useLayoutEffect 和 useEffect 不同的地方是采用了同步执行,那么和useEffect有什么区别呢?
- 首先 useLayoutEffect 是在 DOM 更新之后,浏览器绘制之前,这样可以方便修改 DOM,获取 DOM 信息,这样浏览器只会绘制一次,如果修改 DOM 布局放在 useEffect ,那 useEffect 执行是在浏览器绘制视图之后,接下来又改 DOM ,就可能会导致浏览器再次回流和重绘。而且由于两次绘制,视图上可能会造成闪现突兀的效果。
- useLayoutEffect callback 中代码执行会阻塞浏览器绘制。
使用
import { useLayoutEffect,useState } from "react";
const LayoutEffectHook = () => {
const [count,setCount] = useState(0);
const [age,setAge] = useState(18);
useLayoutEffect(() => {
console.log(`useEffect--${count}`);
return () => {
console.log("useEffect return");
}
}, [count]);
useLayoutEffect(() => {
console.log(`useEffectAge--${age}`);
return () => {
console.log("useEffect returnAge");
}
}, []);
return (
<div>
<h1>Effect Hook</h1>
<h2>组件{count}</h2>
<h2>年龄{age}</h2>
<button onClick={() => setCount(count + 1)}>count+</button>
<button onClick={() => setAge(age + 1)}>age+</button>
</div>
);
}
export default LayoutEffectHook;
useMemo用法
介绍
const cacheSomething = useMemo(create,deps)
- create:第一个参数为一个函数,函数的返回值作为缓存值。
- deps: 第二个参数为一个数组,存放当前 useMemo 的依赖项,在函数组件下一次执行的时候,会对比 deps 依赖项里面的状态,是否有改变,如果有改变重新执行 create ,得到新的缓存值。
- cacheSomething:返回值,执行 create 的返回值。如果 deps 中有依赖项改变,返回的重新执行 create 产生的值,否则取上一次缓存值。
使用
import { useState, useMemo } from "react";
const MemoHook = () => {
const [count, setCount] = useState(0);
const [age, setAge] = useState(18);
const memoizedValue = useMemo(() => {
return count + 2;
}, [count]);
return (
<div>
<div>MemoHook:{memoizedValue}</div>
<div>Count:{count}</div>
<button onClick={() => setCount(count + 1)}>count+</button>
<div>age:{age}</div>
<button onClick={() => setAge(age + 1)}>age+</button>
</div>
)
}
export default MemoHook;
useCallback用法
介绍
useMemo 和 useCallback 接收的参数都是一样,都是在其依赖项发生变化后才执行,都是返回缓存的值,区别在于 useMemo 返回的是函数运行的结果,useCallback 返回的是函数,这个回调函数是经过处理后的也就是说父组件传递一个函数给子组件的时候,由于是无状态组件每一次都会重新生成新的 props 函数,这样就使得每一次传递给子组件的函数都发生了变化,这时候就会触发子组件的更新,这些更新是没有必要的,此时我们就可以通过 usecallback 来处理此函数,然后作为 props 传递给子组件。
使用
import { useState, useCallback } from "react";
const CallbackHook = () => {
const [count, setCount] = useState(0);
const [age, setAge] = useState(18);
const handleCallback = useCallback(() => {
console.log('count:', count, 'age:', age);
}, [count]);
return (
<div>
<div>CallbackHook</div>
<button onClick={handleCallback}>handleCallback</button>
<div>Count:{count}</div>
<button onClick={() => setCount(count + 1)}>count+</button>
<div>age:{age}</div>
<button onClick={() => setAge(age + 1)}>age+</button>
</div>
)
}
export default CallbackHook;
React.memo()语法
介绍
- 当父组件发生改变时,默认情况下它的子孙组件也会重新渲染,当某些子组件不需要更新时,也会被强制更新,为了避免这种情况,我们可以使用 React.memo。
- 类组件中有 shouldComponentUpdate 和 PureComponent 来避免子组件做不必要的渲染。函数组件中的 React.memo() 也有类似的功能,它和 PureComponent 类似,但是只适用于函数组件,默认情况下仅对 props 进行一个浅比较来决定要不要更新,复杂情况下支持自己手写对比的逻辑。
function Demo(props) {
// ...
}
function compare(prevProps, nextProps) {
// 自己写对比逻辑,返回 true 更新,false 跳过更新
// return false
}
export default React.memo(Demo, compare)
- 参数1:组件要渲染的内容。
- 参数2:写的回调函数,一般情况下都在props传过来的数据为引用类型,才需要手动来判断,如果是基本类型则不需要写参数2来手动判断。
如果是引用类型,可以用下面方法进行比较
// 使用lodash库来完成对象的值的比较,从而用来完成减少组件的无用的重复渲染
(prevProps, nextProps) => _.isEqual(prevProps, nextProps)
使用
import { useState } from "react";
import * as React from "react";
import _ from "lodash";
const ReactMemo = () => {
const [count, setCount] = useState(0);
const [name, setName] = useState("ReactMemo");
return (
<div>
<div>ReactMemo:{count}</div>
<div>name:{name}</div>
<input type="text" value={name} onChange={(e) => setName(e.target.value)}></input>
<button onClick={() => setCount(count + 1)}>Click</button>
<ReactMemoChild count={count}></ReactMemoChild>
</div>
);
};
const ReactMemoChild = React.memo(
({count}) => {
console.log("ReactMemoChild");
return <div>ReactMemoChild{count}</div>;
},
(prevProps, nextProps) => {
console.log("prevProps", prevProps, "nextProps", nextProps);
return _.isEqual(prevProps, nextProps);
}
);
export default ReactMemo;
useRef用法
介绍
- useRef 可以用来获取元素,缓存状态,接受一个状态 initState 作为初始值,返回一个 ref 对象 cur, cur 上有一个 current 属性就是 ref 对象需要获取的内容。
- useRef 获取 DOM 元素,在 React Native 中虽然没有 DOM 元素,但是也能够获取组件的节点信息( fiber 信息 )。
- useRef 保存状态, 可以利用 useRef 返回的 ref 对象来保存状态,只要当前组件不被销毁,那么状态就会一直存在。
使用
import { useRef, useState } from "react";
const RefHook = () => {
const [count, setCount] = useState(0);
const [name, setName] = useState("xiaoming");
const inputRef = useRef();
const countRef = useRef(18);
return (
<div>
<div>RefHook--{count}</div>
<input type="text" ref={inputRef} value={name} onChange={(e) => setName(e.target.value)} />
<button
onClick={() => {
inputRef.current.focus();
}}
>
聚焦
</button>
<button
onClick={() => {
inputRef.current.blur();
}}
>
失焦
</button>
<div>countRef.value{countRef.current}</div>
<button onClick={() => setCount(count + 1)}>count+1</button>
<button onClick={() => countRef.current++}>countRef+1</button>
</div>
);
};
export default RefHook;
forwardRef用法
介绍
forwardRef 可以在父组件中操作子组件的 ref 对象,并且将 ref 对象作为一个参数传递给了子组件。
注意
这行代码是关键,加上后子组件就多了个入参ref,来绑定对应子组件的dom元素。
ForwardRefHookChild = forwardRef(ForwardRefHookChild);
使用
import { forwardRef,useRef,useState } from "react";
let ForwardRefHookChild = ({count},ref) => {
return (
<div>
<input type="text" ref={ref} />
<p>ForwardRefHookChild--count{count}</p>
</div>
)
}
const ForwardRefHook = (() => {
const inputRef = useRef();
const [count,setCount] = useState(0);
const handleClick = () => {
inputRef.current.focus();
}
return (
<div>
<button onClick={handleClick}>Focus</button>
<button onClick={()=>{inputRef.current.blur()}}>blur</button>
<button onClick={() => setCount(count + 1)}>Count</button>
<p>ForwardRefHook--count{count}</p>
<ForwardRefHookChild count={count} ref={inputRef} />
</div>
)
})
ForwardRefHookChild = forwardRef(ForwardRefHookChild);
export default ForwardRefHook;
useImperativeHandle用法
介绍
useImperativeHandle 可以配合 forwardRef 自定义暴露给父组件的实例值。这个很有用,我们知道,对于子组件,如果是 class 类组件,我们可以通过 ref 获取类组件的实例,但是在子组件是函数组件的情况,如果我们不能直接通过 ref 的,那么此时 useImperativeHandle 和 forwardRef 配合就能达到效果。
useImperativeHandle 接受三个参数:
- 第一个参数ref: 接受 forWardRef 传递过来的 ref。
- 第二个参数 createHandle :处理函数,返回值作为暴露给父组件的 ref 对象。
- 第三个参数 deps : 依赖项 deps ,依赖项更改形成新的 ref 对象。
使用
import { useImperativeHandle, forwardRef,useRef,useState } from "react";
let ImperativeHandleHook = ()=>{
const childRef = useRef()
return <div>
<ImperativeHandleHookChild ref={childRef}></ImperativeHandleHookChild>
<button onClick={()=>{
childRef.current.handleSetCount()
childRef.current.value = 10
console.log(childRef.current.value);
}}>操作孩子的元素</button>
</div>
}
let ImperativeHandleHookChild = ((props,ref)=>{
const [count,setCount] = useState(0)
useImperativeHandle(ref,()=>({
handleSetCount:()=>{
setCount(count=>count+1)
},
value:6
}))
return <div>
<div>ImperativeHandleHookChild--count{count}</div>
</div>
})
ImperativeHandleHookChild = forwardRef(ImperativeHandleHookChild)
export default ImperativeHandleHook;
useContext用法
介绍
在 React 中传递属性只能一层一层传,如果组件结构比较复杂,层级比较深的时候,数据传递起来就比较麻烦,可能会经过很多次的传递才能将属性传递到目标组件中,那么有没有一种可以在全局进行状态共享的实现方法呢?useContext 就是为了解决这个问题的,可以实现不必层层传递就能共享状态的功能。具体用法看下面步骤:
使用
import React, { useContext } from "react";
let userContext = React.createContext();
const ContextHook = () => {
return (
<div>
<h1>Context Hook</h1>
<userContext.Provider value={{ name: "Rahul", age: 25 }}>
<ContextHookChild1></ContextHookChild1>
</userContext.Provider>
</div>
);
};
const ContextHookChild1 = () => {
return (
<div>
<h1>ContextHookChild1</h1>
<ContextHookChild2></ContextHookChild2>
</div>
);
};
const ContextHookChild2 = () => {
const user = useContext(userContext);
return (
<div>
<div>user.name{user.name}</div>
<div>user.age{user.age}</div>
</div>
);
};
export default ContextHook;
useReducer用法
介绍
useReducer 是 react-hooks 提供的能够在无状态组件中运行的类似redux的功能 api 。
useReducer 基础介绍:
const [ state , dispatch ] = useReducer(reducer,object)
- state:更新之后的 state 值。
- dispatch:派发更新的 dispatchAction 函数, 本质上和 useState 的 dispatchAction 是一样的。
- reducer: 一个函数 reducer ,我们可以认为它就是一个 redux 中的 reducer , reducer的参数就是常规reducer里面的state和action, 返回改变后的state, 这里有一个需要注意的点就是:如果返回的 state 和之前的 state ,内存指向相同,那么组件将不会更新。
- object:接受的变量
使用
import { useReducer } from "react";
const ReducerHook = () => {
let reducer = ((state, action) => {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
return state;
}
})
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<h6>Count: {state.count}</h6>
<button onClick={() => dispatch({ type: "increment" })}>Increment</button>
<button onClick={() => dispatch({ type: "decrement" })}>Decrement</button>
</div>
)
}
export default ReducerHook;