-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
HOC 和 Hooks —— React组件复用 #51
Comments
HookHook是 渐进策略,React 没有计划移除 class。最重要的是,Hook 和现有代码可以同时工作,你可以渐进式地使用他们。 不用急着迁移到 Hook。只要是在函数中就可以使用 Hook。 Hook的动机React官方文档中详细阐述了Hook的动机 概括如下: 1、组件之间复用状态逻辑很难通常我们使用renderProps或者HOC来做逻辑复用,但是这也需要你重新组织组件结构。Hook 使你在无需修改组件结构的情况下复用状态逻辑。 2、复杂组件变得难以理解在业务逻辑复杂时,同一个生命周期周会包含很多不想关的逻辑,使得开发者在理解组件时很困难。 3、难以理解的 class在编写类组件时,你必须去理解 JavaScript 中 this 的工作方式,这与其他语言存在巨大差异。还不能忘记绑定事件处理器。 Hook 使你在非 class 的情况下可以使用更多的 React 特性。 官方 Hook 有哪些基础 Hook
额外的 Hook
基础HookuseState在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同。 setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。 setState也可以使用函数方式来更新state,函数将接收一个prevState,可以用来解决闭包中陈旧值的问题。 const [state, setState] = useState({});
setState(prevState => {
// 也可以使用 Object.assign
return {...prevState, ...updatedValues};
}); 另外,由于hook 的 setState 不像 class中的setState会自动进行属性合并,所以可以自己通过扩展操作符来实现合并。 useEffectEffect Hook 可以让你在函数组件中执行一些具有 side effect(副作用)的操作。 默认情况下,effect 将在每轮组件渲染到屏幕之后执行,但你可以选择让它 在只有某些值改变的时候 才执行。 useEffect接收两个参数:
useContext接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。 当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。即使祖先使用 React.memo 或 shouldComponentUpdate,也会在组件本身使用 useContext 时重新渲染。所以要注意不要滥用useContext。 如何避免 useContext造成的不必要的渲染1、拆分context,将不会同时发生变更的数据拆分到不同的context中。 类组件中的表示
额外的 HookuseLayoutEffect其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。 useReduceruseState 的替代方案。 它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。) 在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。 并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数,回调函数在不使用 useCalback 进行优化的情况下,组件刷新的时候会重新创建,从而导致子组件重新渲染 。 需要注意的是,React内部使用Object.is 比较算法 来比较state前后是否发生变更,来判断是否需要更新组件,所以和React-Redux一样,reducer的返回值必须是一个新的对象,而不应该在原有state的基础上修改属性值。 另外,由于reducer中总是能够拿到最新的state,所以也可以避免掉闭包导致的 state 陈旧值的问题。 用法: const [state, dispatch] = useReducer(reducer, initialArg, init); 使用案例(计数器):
useCalback通常用来保存函数的 memoized 版本。 应用场景: useCalback的实现(借助 useMemo)由于 **useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。**我们可以很简单地通过 useMemo 来实现useCallback: function useCalback (fn, deps) {
let memoizedFn = useMemo(() => fn, deps)
return memoizedFn
} useMemo
返回一个memoized值,当依赖项没有发生变更时,就不会重新执行创建函数,减少不必要的计算开销。 如果不提供依赖项,则每次都会重新计算。 你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。 useRef
useRef() 比 ref 属性更有用。它可以很方便地保存任何可变值,其类似于在 class 中使用实例字段的方式。 这是因为它创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current: ...} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。 需要注意的是,修改ref对象的current属性时,不会触发组件的重新渲染。 useImperativeHandleuseImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用: function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
// 将输入框的focus方法暴露给父组件
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput); 在本例中,渲染 的父组件可以调用 inputRef.current.focus()。 useDebugValueuseDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签。 例如,“自定义 Hook” 章节中描述的名为 useFriendStatus 的自定义 Hook: function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
// 在开发者工具中的这个 Hook 旁边显示标签
// e.g. "FriendStatus: Online"
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
} 我们不推荐你向每个自定义 Hook 添加 debug 值。当它作为共享库的一部分时才最有价值。 使用 Hook 的注意事项
Hook 是通过数组实现的,React需要利用 Hook 的调用顺序来正确更新相应的状态,如果 useState 被包裹循环或条件语句中,那每就可能会引起调用顺序的错乱,从而造成意想不到的错误。 我们可以安装一个eslint插件来帮助我们避免这些问题。
自定义Hook像上面介绍的HOC和mixin一样,我们同样可以通过自定义的Hook将组件中类似的状态逻辑抽取出来。 例举自定义 Hook 的封装: 修改页面title: function useTitle(title) {
useEffect(
() => {
document.title = title;
return () => (document.title = "主页");
},
[title]
);
}
function Page1(props){
useTitle('Page1');
return (<div>...</div>)
} 页面埋点: function useTrack(title) {
useEffect(
() => {
track.pageTrack(title)
},
[]
);
} 禁止页面滚动: useBlockScroll(title) {
const originOverflow = document.body.style.overflow
useEffect(
() => {
document.body.style.height = "100%"
document.body.style.overflow = "hidden"
return () => {
document.body.style.height = "auto"
document.body.style.overflow = originOverflow
}
},
[]
);
} Hook中闭包陷阱的原理,如何解决 |
Hook中闭包陷阱的原理,如何解决来看一个典型的使用 useEffect 时导致的闭包陷阱:
在函数组件更新时,是会重新执行函数的,也就会形成一个新的上下文。 那么我们根据作用域的访问规则可以知道,effect 中的事件监听器访问了 state,会形成闭包,并且一直指向的都是第一轮更新时上下文中的 state 。后续就算state发生了变更,也不会对上一轮更新的state产生修改,所以回调函数中访问到的state一直都是旧值。 总结原因: 还有一点是,通过声明依赖只是可以更新effect函数,在下轮更新时执行新的effect,但是老的effect函数中产生的闭包并不一定能够被清除,比如说通过setInterval注册的回调,需要在下一轮的effect执行时主动去清除,否则这个闭包会在执行时不断产生。 解决方法1、通过useRef来获取最新的值 |
Mixin
Mixin(混入)是一种通过扩展收集功能的方式,它本质上是将一个对象的属性拷贝到另一个对象上面去,不过你可以拷贝任意多个对象的任意个方法到一个新对象上去,这是继承所不能实现的。它的出现主要就是为了解决代码复用问题。
React中应用Mixin
React也提供了Mixin的实现,如果完全不同的组件有相似的功能,我们可以引入来实现代码复用,当然只有在使用createClass来创建React组件时才可以使用,因为在React组件的es6写法中它已经被废弃掉了。
Mixin存在一些缺陷:
装饰模式
装饰者(decorator)模式能够在不改变对象自身的基础上,在程序运行期间给对像动态的添加职责。与继承相比,装饰者是一种更轻便灵活的做法。
高阶组件HOC(Higher-Order-Components)
高阶组件可以看作React对装饰模式的一种实现,高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。
高阶组件的实现方式有属性代理和反向继承
高阶组件可以实现什么功能
高阶函数的应用场景有:
React-Redux中的connect
React-Redux中的connect其实就是一个高阶函数,通过代理组件的props来实现将state和dispatch方法作为props注入原组件中。
使用HOC的一些注意事项,也可以说是缺陷
The text was updated successfully, but these errors were encountered: