redux hooks
- 在之前的redux开发中,为了让组件和redux结合起来,使用了react-redux中的connect:
- 但是这种方式必须使用高阶函数结合返回的高阶组件;
- 并且必须编写:
mapStateToProps和mapDispatchToProps映射的函数;
- 在Redux7.1开始,提供了Hook的方式,不需要编写connect以及对应的映射函数了
useSelector的作用是将state映射到组件中:- 参数一:将state映射到需要的数据中;
- 参数二:可以进行比较来决定是否组件重新渲染;
useSelector默认会比较我们返回的两个对象是否相等;- 如何比较呢? const refEquality = (a, b) => a === b;
- 也就是我们必须
返回两个完全相等的对象才可以不引起重新渲染; 5.useDispatch就是直接获取dispatch函数,之后在组件中直接使用即可;
- 还可以通过
useStore来获取当前的store对象;
// 使用React.memo来对组件进行性能优化,只有当组件的props改变时,才会重新渲染组件
const App = memo((props) => {
// 使用useSelector从Redux的store中取出state,这里只取出了state.counter.count,并将其命名为count
// shallowEqual是浅比较函数,用于比较前后两次state的更改是否真的影响到了组件,有助于减少不必要的渲染
const { count } = useSelector((state) => ({
count: state.counter.count
}), shallowEqual)
// 使用useDispatch获取dispatch函数,用于向Redux store发送action
const dispatch = useDispatch()
// 定义一个函数,该函数根据isAdd的值来决定是派发加法action还是减法action
function addNumberHandle(num, isAdd = true) {
if (isAdd) {
dispatch(addNumberAction(num)) // 派发加法action
} else {
dispatch(subNumberAction(num)) // 派发减法action
}
}
// 每次组件渲染时,都会在控制台输出"App render"
console.log("App render")
return (
// 渲染组件
<div>
// 显示当前的计数
<h2>当前计数: {count}</h2>
// 按钮,点击后会调用addNumberHandle函数,传入1,增加计数
<button onClick={e => addNumberHandle(1)}>+1</button>
// 按钮,点击后会调用addNumberHandle函数,传入6,增加计数
<button onClick={e => addNumberHandle(6)}>+6</button>
// 按钮,点击后会调用addNumberHandle函数,传入6并且设置isAdd为false,减少计数
<button onClick={e => addNumberHandle(6, false)}>-6</button>
// 渲染Home组件
<Home/>
</div>
)
})
useId
useId是一个用于生成横跨服务端和客户端的稳定的唯一 ID 的同时避免 hydration 不匹配的 hook。
什么是SSR?
- SSR(Server Side Rendering,服务端渲染),指的是页面在服务器端已经生成了完整的HTML页面结构,不需要浏览器解析;
- 对应的是CSR(Client Side Rendering,客户端渲染),我们开发的SPA页面通常依赖的就是客户端渲染;
- 不过我们可以借助于Node来帮助我们执行JavaScript代码,提 前完成页面的渲染;
SSR同构应用
- 什么是同构?
- 一套代码既可以在服务端运行又可以在客户端运行,这就是同构应用。
- 同构是一种SSR的形态,是现代SSR的一种表现形式。
- 当用户发出请求时,先在服务器通过SSR渲染出首页的内容。
- 但是对应的代码同样可以在客户端被执行。
- 执行的目的包括事件绑定等以及其他页面切换时也可以在客户端被渲染;
Hydration
- 在进行 SSR 时,我们的页面会呈现为 HTML。
- 但仅 HTML 不足以使页面具有交互性。例如,浏览器端 JavaScript 为零的页面不能是交互式的(没有 JavaScript事件处理程序来响应用户操作,例如单击按钮)。
- 为了使我们的页面具有交互性,除了在 Node.js 中将页面呈现为 HTML 之外,我们的 UI 框架(Vue/React/...)还在浏览器中加载和呈现页面。(它创建页面的内部表示,然后将内部表示映射到我们在 Node.js 中呈现的 HTML 的 DOM 元素。)
- 这个过程称为hydration。
- hydration(填充)是一个过程,在该过程中,客户端的JavaScript使得服务器渲染的HTML变得完全可交互。
-
当一个React应用程序被服务器渲染时,服务器将HTML发送到浏览器,HTML在JavaScript包仍在加载时就会被立即显示。一旦JavaScript加载完成,React就会通过添加事件监听器“填充”已经存在的HTML,从而使网站变得可交互。
-
这个过程对于性能有利,尤其是对于首次绘制和首次有意义的绘制,因为用户可以在JavaScript加载完成之前看到完全形态的应用程序。hydration的主要优势是提高用户感知的页面加载性能。
以下是一个简单的例子:
import { hydrate } from 'react-dom'
import App from './App'
hydrate(<App />, document.getElementById('root'))
在这个例子中,使用了来自'react-dom'的hydrate函数,而不是ReactDOM.render。hydrate函数假设document.getElementById('root')中的HTML内容是<App />组件的服务器渲染版本,它只是附加适当的事件处理器使其可交互。
useId的作用
useId 是一个用于生成横跨服务端和客户端的稳定的唯一 ID 的同时避免 hydration 不匹配的 hook。
useId是用于react的同构应用开发的,前端的SPA页面并不需要使用它;- useId可以
保证应用程序在客户端和服务器端生成唯一的ID,这样可以有效的避免通过一些手段生成的id不一致,造成hydration mismatch; - 在React中,
"hydration mismatch"指的是在服务器渲染(SSR)中,服务器生成的HTML与客户端生成的HTML不匹配的情况。当React试图在客户端上"hydrate"服务器生成的HTML时,它希望该HTML与客户端渲染的HTML完全匹配。如果这两者不匹配,React将无法正确地将事件处理程序附加到正确的元素,并可能导致意外的行为。
// 使用React.memo创建一个纯函数组件App
const App = memo(() => {
// 使用useState创建一个状态变量count,其初始值为0
const [count, setCount] = useState(0)
// 使用useId hook生成一个唯一的id
const id = useId()
// 打印生成的唯一id
console.log(id)
// 返回React组件的JSX
return (
<div>
// 创建一个按钮,点击时会调用setCount函数将count状态变量增加1,并在按钮上显示当前的count值
<button onClick={e => setCount(count+1)}>count+1:{count}</button>
// 创建一个标签(label),其for属性值为之前生成的唯一id
<label htmlFor={id}>
// 在标签内添加文字“用户名”
用户名:
// 创建一个文本输入框(input),其id属性值也为之前生成的唯一id,这样点击标签时,对应的输入框将会获得焦点
<input id={id} type="text" />
</label>
</div>
)
})
useTransition
告诉react对于
某部分任务的更新优先级较低,可以稍后进行更新。
这段代码在React的functional component中使用了useTransition hook。以下是每一句代码的注释:
// 使用React.memo创建一个纯函数组件App
const App = memo(() => {
// 使用useState创建一个状态变量showNames,其初始值为namesArray
const [showNames, setShowNames] = useState(namesArray)
// 使用useTransition创建一个等待状态pending和启动过渡的函数startTransition
const [pending, startTransition] = useTransition()
// 定义一个处理函数,在文本输入框内容改变时被触发
function valueChangeHandle(event) {
// 启动过渡,传入一个更新状态的函数
startTransition(() => {
// 获取输入框的内容
const keyword = event.target.value
// 根据输入框的内容过滤namesArray,只保留包含关键词的项
const filterShowNames = namesArray.filter(item => item.includes(keyword))
// 更新showNames状态
setShowNames(filterShowNames)
})
}
// 返回React组件的JSX
return (
<div>
// 创建一个文本输入框,输入时触发valueChangeHandle函数
<input type="text" onInput={valueChangeHandle}/>
// 显示标题,如果pending为真(表示过渡未完成),则显示“data loading”
<h2>用户名列表: {pending && <span>data loading</span>} </h2>
// 创建一个列表,列表项为showNames中的每一项
<ul>
{
showNames.map((item, index) => {
// 返回每一项的JSX
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
})
这个代码的主要目的是根据用户输入进行过滤,并用过渡效果显示过滤结果。useTransition hook在React concurrent mode中用于创建一个过渡,其可以让React在渲染新的UI时保持旧的UI一段时间,以防止在数据加载过程中出现短暂的空白或闪烁。
useDeferredValue
useDeferredValue的作用是一样的效果,可以让我们的更新延迟。
这段代码在React的functional component中使用了useDeferredValue hook。以下是每一句代码的注释:
// 使用React.memo创建一个纯函数组件App
const App = memo(() => {
// 使用useState创建一个状态变量showNames,其初始值为namesArray
const [showNames, setShowNames] = useState(namesArray)
// 使用useDeferredValue创建一个延迟版本的状态值deferedShowNames
const deferedShowNames = useDeferredValue(showNames)
// 定义一个处理函数,在文本输入框内容改变时被触发
function valueChangeHandle(event) {
// 获取输入框的内容
const keyword = event.target.value
// 根据输入框的内容过滤namesArray,只保留包含关键词的项
const filterShowNames = namesArray.filter(item => item.includes(keyword))
// 更新showNames状态
setShowNames(filterShowNames)
}
// 返回React组件的JSX
return (
<div>
// 创建一个文本输入框,输入时触发valueChangeHandle函数
<input type="text" onInput={valueChangeHandle}/>
// 显示标题
<h2>用户名列表: </h2>
// 创建一个列表,列表项为deferedShowNames中的每一项
<ul>
{
deferedShowNames.map((item, index) => {
// 返回每一项的JSX
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
})
这个代码的主要目的是根据用户输入进行过滤,并用延迟值显示过滤结果。useDeferredValue hook在React concurrent mode中用于创建一个延迟版本的状态值,这可以让React在处理高优先级更新时,延迟处理低优先级的状态更新,如本例中的列表渲染,从而优化性能,提升用户体验。
useTransition和useDeferredValue的异同
useTransition 和 useDeferredValue 都是 React Concurrent Mode 中的新特性,它们都允许你调整组件的更新策略,以便在性能敏感或者资源有限的情况下,优先处理更重要的更新。但它们的具体用途和使用方式有所不同。
-
useTransition是用来处理状态更新引发的重渲染过程。当你调用startTransition函数去更新状态时,React 会把这个更新标记为 "transition",这意味着这个更新可以被推迟,如果有更高优先级的更新需要处理(比如用户的交互事件)。使用
useTransition时,你可以控制状态更新的延迟程度,它提供了一个isPending状态,当延迟的状态更新正在进行时,这个状态就为true,你可以根据这个状态展示一些临时的 UI(如加载指示器)。 -
useDeferredValue则是用来处理状态值本身。这个 hook 接收一个状态值作为参数,并返回一个 "延迟" 的版本的这个状态值。当原始状态值更新时,React 不会立即更新这个 "延迟" 的版本,而是会等待一段时间。这段时间取决于当前的系统资源和其他的任务优先级。useDeferredValue更适用于处理大量数据的情况,比如输入框和一个大型列表,每次输入都会对列表进行过滤。使用useDeferredValue可以让列表的更新推迟,以保证输入框的响应速度。
总的来说,useTransition 和 useDeferredValue 都是为了提供更流畅的用户体验,在复杂的更新中保持应用的响应速度。但具体哪个适合使用,需要根据实际的场景和需求来决定。
自定义Hook
自定义Hook本质上只是一种函数代码逻辑的抽取
Context的共享
function useUserToken() {
const user = useContext(UserContext)
const token = useContext(TokenContext)
return [user, token]
}
const Home = memo(() => {
const [user, token] = useUserToken()
return <h1>Home Page: {user.name}-{token}</h1>
})
获取滚动位置
这段代码定义了一个名为useScrollPosition的自定义 Hook,它用来获取并返回浏览器窗口的滚动位置。
// 定义自定义hook:useScrollPosition
function useScrollPosition() {
// 使用useState钩子定义滚动位置的状态和更新函数,初始位置设为0
const [ scrollX, setScrollX ] = useState(0)
const [ scrollY, setScrollY ] = useState(0)
// 使用useEffect钩子在组件挂载时添加滚动事件监听,卸载时移除事件监听
useEffect(() => {
// 定义滚动事件处理函数,该函数会更新scrollX和scrollY的状态
function handleScroll() {
// 获取window对象的滚动位置
// console.log(window.scrollX, window.scrollY)
// 使用setState函数更新滚动位置
setScrollX(window.scrollX)
setScrollY(window.scrollY)
}
// 在window对象上添加滚动事件监听,指定handleScroll为处理函数
window.addEventListener("scroll", handleScroll)
// 返回一个函数,该函数在组件卸载时执行,用于清理副作用(这里的副作用就是滚动事件监听)
return () => {
// 在window对象上移除滚动事件监听
window.removeEventListener("scroll", handleScroll)
}
// 由于依赖列表为空,所以这个副作用只在组件挂载时运行一次,卸载时清理
}, [])
// 返回滚动位置的状态值,scrollX表示横向滚动位置,scrollY表示纵向滚动位置
return [scrollX, scrollY]
}
这样,任何组件只需要调用这个useScrollPosition Hook,就能获取到窗口的滚动位置,并在滚动时实时更新。
Home组件,它使用了上述useScrollPosition自定义Hook。
// 定义一个名为Home的组件,并使用memo函数包裹,该函数会对组件进行优化,防止不必要的重渲染
const Home = memo(() => {
// 使用自定义Hook useScrollPosition,该Hook返回一个包含scrollX和scrollY的数组,分别表示窗口的横向滚动位置和纵向滚动位置
const [scrollX, scrollY] = useScrollPosition()
// 组件的渲染内容是一个h1元素,显示的文本是"Home Page: "后跟滚动位置
return <h1>Home Page: {scrollX}-{scrollY}</h1>
})
这样,当你在浏览器中滚动页面时,Home组件中的h1元素将显示当前窗口的滚动位置。
localStorage数据存储
function useLocalStorage(key) {
// 1.从localStorage中获取数据, 并且数据数据创建组件的state
const [data, setData] = useState(() => {
const item = localStorage.getItem(key)
if (!item) return ""
return JSON.parse(item)
})
// 2.监听data改变, 一旦发生改变就存储data最新值
useEffect(() => {
localStorage.setItem(key, JSON.stringify(data))
}, [data])
// 3.将data/setData的操作返回给组件, 让组件可以使用和修改值
return [data, setData]
}
const App = memo(() => {
const [token, setToken] = useLocalStorage("token")
function setTokenHandle() {
setToken("james")
}
return (
<div className='app'>
<h1>App Root Component: {token}</h1>
<button onClick={setTokenHandle}>设置token</button>
</div>
)
})