在组件之间复用状态逻辑很难html
React 没有提供将可复用性行为“附加”到组件的途径(例如,把组件链接到 store)。有一些解决此类问题的方案,好比 render props 和 高阶组件。可是这类方案须要从新组织你的组件结构,这可能会很麻烦,使你的代码难以理解。node
复杂组件变得难以理解react
组件经常在 componentDidMount
和 componentDidUpdate
中获取数据。可是,同一个 componentDidMount
中可能也包含不少其它的逻辑,如设置事件监听,而以后需在 componentWillUnmount
中清除。相互关联且须要对照修改的代码被进行了拆分,而彻底不相关的代码却在同一个方法中组合在一块儿。如此很容易产生 bug,而且致使逻辑不一致。ajax
难以理解的 class算法
class 是学习 React 的一大屏障。你必须去理解 JavaScript 中 this
的工做方式,这与其余语言存在巨大差别。还不能忘记绑定事件处理器。没有稳定的语法提案,这些代码很是冗余。你们能够很好地理解 props,state 和自顶向下的数据流,但对 class 却束手无策。编程
useState
是react自带的一个hook函数,它的做用就是用来声明状态变量。useState
这个函数接收的参数是咱们的状态初始值(initial state),它返回了一个数组,这个数组的第[0]
项是当前当前的状态值,第[1]
项是能够改变状态值的方法函数。segmentfault
//返回一个 state,以及更新 state 的函数 setState(接收一个新的 state 值并将组件的一次从新渲染加入队列)
const [state, setState] = useState(initialState);
复制代码
//若是新的 state 须要经过使用先前的 state 计算得出,那么能够将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值。
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> </> ); } 复制代码
//若是初始 state 须要经过复杂计算得到,则能够传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
复制代码
调用 State Hook 的更新函数并传入当前的 state 时,React 将跳过子组件的渲染及 effect 的执行。(React 使用 Object.is
比较算法 来比较 state。)api
咱们写的有状态组件,一般会产生不少的反作用(side effect),好比发起ajax请求获取数据,添加一些监听的注册和取消注册,手动修改dom等等。咱们以前都把这些反作用的函数写在生命周期函数钩子里,好比componentDidMount
,componentDidUpdate
和componentWillUnmount
。而如今的useEffect就至关与这些声明周期函数钩子的集合体。它以一抵三。数组
import { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 相似于componentDidMount 和 componentDidUpdate:
useEffect(() => {
// 更新文档的标题
document.title = `You clicked ${count} times`;
});
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div>
);
}
复制代码
一般,组件卸载时须要清除 effect 建立的诸如订阅或计时器 ID 等资源。要实现这一点,useEffect
函数需返回一个清除函数。如下就是一个建立订阅的例子:浏览器
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// 清除订阅
subscription.unsubscribe();
};
});
复制代码
为防止内存泄漏,清除函数会在组件卸载前执行。另外,若是组件屡次渲染(一般如此),则在执行下一个 effect 以前,上一个 effect 就已被清除。
与 componentDidMount
、componentDidUpdate
不一样的是,在浏览器完成布局与绘制以后,传给 useEffect
的函数会延迟调用。这使得它适用于许多常见的反作用场景,好比设置订阅和事件处理等状况,所以不该在函数中执行阻塞浏览器更新屏幕的操做。
默认状况下,effect 会在每轮组件渲染完成后执行。这样的话,一旦 effect 的依赖发生变化,它就会被从新建立。在某些状况下,咱们不须要在每次组件更新时都建立新的订阅,而是仅须要在 source
prop 改变时从新建立。要实现这一点,能够给 useEffect
传递第二个参数,它是 effect 所依赖的值数组。
//此时,只有当 props.source 改变后才会从新建立订阅。(要实现componentDidMount功能只须要设置第二个参数为[]便可)
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source],
);
复制代码
能够深层组件传值,父组件传给子孙组件。接收一个 context 对象(React.createContext
的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider>
的 value
prop 决定。
当组件上层最近的 <MyContext.Provider>
更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext
provider 的 context value
值。即便祖先使用 React.memo
或 shouldComponentUpdate
,也会在组件自己使用 useContext
时从新渲染。
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); return ( <button style={{ background: theme.background, color: theme.foreground }}> I am styled by theme context! </button> ); } 复制代码
useState
的替代方案,能够用于复杂状态处理。它接收一个形如 (state, action) => newState
的 reducer,并返回当前的 state 以及与其配套的 dispatch
方法。(若是你熟悉 Redux 的话,就已经知道它如何工做了。)
有两种不一样初始化 useReducer
state 的方式,你能够根据使用场景选择其中的一种。将初始 state 做为第二个参数传入 useReducer
是最简单的方法:
//nst [state, dispatch] = useReducer(reducer, initialArg, init);
const [state, dispatch] = useReducer(
reducer,
{count: initialCount}
);
复制代码
某些场景下,useReducer
会比 useState
更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于以前的 state 等。而且,使用 useReducer
还能给那些会触发深更新的组件作性能优化,由于你能够向子组件传递 dispatch
而不是回调函数 。
const initialState = {count: 0};
function reducer(state, action) {
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> </> ); } 复制代码
你能够选择惰性地建立初始 state。为此,须要将 init
函数做为 useReducer
的第三个参数传入,这样初始 state 将被设置为 init(initialArg)
。
这么作能够将用于计算 state 的逻辑提取到 reducer 外部,这也为未来对重置 state 的 action 作处理提供了便利:
function init(initialCount) {
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);
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> </> ); } 复制代码
若是 Reducer Hook 的返回值与当前 state 相同,React 将跳过子组件的渲染及反作用的执行。(React 使用 Object.is
比较算法 来比较 state。)
把“建立”函数和依赖项数组做为参数传入 useMemo
,它仅会在某个依赖项改变时才从新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。若是没有提供依赖项数组,useMemo
在每次渲染时都会计算新的值。memo
是浅比较,意思是,对象只比较内存地址,只要你内存地址没变,管你对象里面的值变幻无穷都不会触发render。
**你能够把 useMemo
做为性能优化的手段,但不要把它当成语义上的保证。**未来,React 可能会选择“遗忘”之前的一些 memoized 值,并在下次渲染时从新计算它们,好比为离屏组件释放内存。先编写在没有 useMemo
的状况下也能够执行的代码 —— 以后再在你的代码中添加 useMemo
,以达到优化性能的目的。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
复制代码
把内联回调函数及依赖项数组做为参数传入 useCallback
,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给通过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate
)的子组件时,它将很是有用。
useMemo
与 useCallback
相似,都是有着缓存的做用,useMemo 是缓存值的,useCallback 是缓存函数的。
useCallback(fn, deps)
至关于 useMemo(() => fn, deps)
。
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
复制代码
useRef
返回一个可变的 ref 对象,其 .current
属性被初始化为传入的参数(initialValue
)。返回的 ref 对象在组件的整个生命周期内保持不变。
useEffect
里面的state的值,是固定的,这个是有办法解决的,就是用useRef
,能够理解成useRef
的一个做用:就是至关于全局做用域,一处被修改,其余地方全更新。
本质上,useRef
就像是能够在其 .current
属性中保存一个可变值的“盒子”。你应该熟悉 ref 这一种访问 DOM 的主要方式。若是你将 ref 对象以 <div ref={myRef} />
形式传入组件,则不管该节点如何改变,React 都会将 ref 对象的 .current
属性设置为相应的 DOM 节点。然而,useRef()
比 ref
属性更有用。它能够很方便地保存任何可变值,其相似于在 class 中使用实例字段的方式。
请记住,当 ref 对象内容发生变化时,useRef
并不会通知你。变动 .current
属性不会引起组件从新渲染。若是想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则须要使用回调 ref 来实现。
const Hook =()=>{
const [count, setCount] = useState(0)
const btnRef = useRef(null)
useEffect(() => {
console.log('use effect...')
const onClick = ()=>{
setCount(count+1)
}
btnRef.current.addEventListener('click',onClick, false)
return ()=> btnRef.current.removeEventListener('click',onClick, false)
},[count])
return(
<div> <div> {count} </div> <button ref={btnRef}>click me </button> </div>
)
}
复制代码
自定义 Hook 是一个函数,其名称以 “use
” 开头,函数内部能够调用其余的 Hook。
例如,下面的 useFriendStatus
是咱们第一个自定义的 Hook:
import { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
复制代码
自定义一个当resize 的时候 监听window的width和height的hook
import {useEffect, useState} from "react";
export const useWindowSize = () => {
const [width, setWidth] = useState()
const [height, setHeight] = useState()
useEffect(() => {
const {clientWidth, clientHeight} = document.documentElement
setWidth(clientWidth)
setHeight(clientHeight)
}, [])
useEffect(() => {
const handleWindowSize = () =>{
const {clientWidth, clientHeight} = document.documentElement
setWidth(clientWidth)
setHeight(clientHeight)
};
window.addEventListener('resize', handleWindowSize, false)
return () => {
window.removeEventListener('resize',handleWindowSize, false)
}
})
return [width, height]
}
复制代码
使用:
const [width, height] = useWindowSize()
const isOnline = useFriendStatus(id);
复制代码