学习内容来源:React + React Hook + TS 最佳实践-慕课网
相对原教程,我在学习开始时(2023.03)采用的是当前最新版本:
项 | 版本 |
---|---|
react & react-dom | ^18.2.0 |
react-router & react-router-dom | ^6.11.2 |
antd | ^4.24.8 |
@commitlint/cli & @commitlint/config-conventional | ^17.4.4 |
eslint-config-prettier | ^8.6.0 |
husky | ^8.0.3 |
lint-staged | ^13.1.2 |
prettier | 2.8.4 |
json-server | 0.17.2 |
craco-less | ^2.0.0 |
@craco/craco | ^7.1.0 |
qs | ^6.11.0 |
dayjs | ^1.11.7 |
react-helmet | ^6.1.0 |
@types/react-helmet | ^6.1.6 |
react-query | ^6.1.0 |
@welldone-software/why-did-you-render | ^7.0.1 |
@emotion/react & @emotion/styled | ^11.10.6 |
具体配置、操作和内容会有差异,“坑”也会有所不同。。。
- 一、项目起航:项目初始化与配置
- 二、React 与 Hook 应用:实现项目列表
- 三、 TS 应用:JS神助攻 - 强类型
- 四、 JWT、用户认证与异步请求(上)
- 四、 JWT、用户认证与异步请求(下)
- 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(上)
- 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(下)
- 六、用户体验优化 - 加载中和错误状态处理(上)
- 六、用户体验优化 - 加载中和错误状态处理(中)
- 六、用户体验优化 - 加载中和错误状态处理(下)
- 七、Hook,路由,与 URL 状态管理(上)
- 七、Hook,路由,与 URL 状态管理(中)
- 七、Hook,路由,与 URL 状态管理(下)
- 八、用户选择器与项目编辑功能(上)
- 八、用户选择器与项目编辑功能(下)
- 九、深入React 状态管理与Redux机制(一)
- 九、深入React 状态管理与Redux机制(二)
- Redux 中文官网 - JavaScript 应用的状态容器,提供可预测的状态管理。 | Redux 中文官网
- https://www.github.com/reduxjs/redux
Predictable state container for JavaScript apps —— 用于JavaScript应用程序的可预测的状态容器
redux
与 react
并没有直接关系,它也可以用在 vue
或其他 js/ts 项目中redux
称为状态容器比状态管理工具要更准确接下来看一个官方提供的在普通 html + js
中使用 redux
的案例
examples/counter-vanilla
<!DOCTYPE html>
<html>
<head>
<title>Redux basic example</title>
<script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
</head>
<body>
<div>
<p>
Clicked: <span id="value">0</span> times
<button id="increment">+</button>
<button id="decrement">-</button>
<button id="incrementIfOdd">Increment if odd</button>
<button id="incrementAsync">Increment async</button>
</p>
</div>
<script>
function counter(state, action) {
if (typeof state === 'undefined') {
return 0
}
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
var store = Redux.createStore(counter)
var valueEl = document.getElementById('value')
function render() {
valueEl.innerHTML = store.getState().toString()
}
render()
store.subscribe(render)
document.getElementById('increment')
.addEventListener('click', function () {
store.dispatch({ type: 'INCREMENT' })
})
document.getElementById('decrement')
.addEventListener('click', function () {
store.dispatch({ type: 'DECREMENT' })
})
document.getElementById('incrementIfOdd')
.addEventListener('click', function () {
if (store.getState() % 2 !== 0) {
store.dispatch({ type: 'INCREMENT' })
}
})
document.getElementById('incrementAsync')
.addEventListener('click', function () {
setTimeout(function () {
store.dispatch({ type: 'INCREMENT' })
}, 1000)
})
</script>
</body>
</html>
有没有感觉和之前写的 use-undo 十分类似?
可预测的(Predictable):对于相同入参,函数的返回值以及它产生的影响是一定的
Redux中的注意事项
redux
中的state
是immutable
,只能被替换,不能被改变。其设计理念与react
中的state
是一致的,它们都是通过===
来比较state
是否更新了。redux
中的reducer
必须是一个纯函数。但这并不代表不能使用异步函数,你完全可以在一个异步函数的回调中去使用dispatch
为什么不直接返回原state,而是要替换?
因为比较两个 javascript 对象中所有的属性是否完全相同,唯一的办法就是深比较,然而,深比较在真实的应用中代码是非常大的,非常耗性能的,需要比较的次数特别多,所以一个有效的解决方案就是做一个规定,当无论发生任何变化时,开发者都要返回一个新的对象。
何谓纯函数?
- 不依赖外部环境状态,只依赖于其输入参数 —— 相同的输入永远返回相同的输出
- 无任何副作用 —— 不修改函数的输入值,不会产生任何可观察的副作用,例如网络请求,输入和输出设备或数据突变(mutation)
- 什么是纯函数_以及为什么要用纯函数?
容器组件与展示组件分离
- React Redux 中文文档
- https://www.github.com/nefe/react-redux-cn
以下内容是课件原文:
Hook 的发展历程
React团队从一开始就很注重 React 的代码复用性。
.
他们对代码复用性的解决方案历经:Mixin, HOC, Render Prop,直到现在的 Custom Hook
.
所以 Custom Hook 并不是一拍脑门横空出世的产物,即使是很多对 Custom Hook 有丰富开发经验的开发者,也不了解 Hook 到底是怎么来的,以及在 React 里扮演什么角色
.
不理解这段设计思路是无法深刻的理解 Custom Hook 的,今天我们就一起来学习一下。1. Mixin
var SetIntervalMixin = { componentWillMount: function() { this.intervals = []; }, setInterval: function() { this.intervals.push(setInterval.apply(null, arguments)); }, componentWillUnmount: function() { this.intervals.forEach(clearInterval); } }; var createReactClass = require('create-react-class'); var TickTock = createReactClass({ mixins: [SetIntervalMixin], //使用mixin getInitialstate: function() { return {seconds: 0;}, }, componentDidMount: function() { this.setInterval(this.tick, 1000);//调用mixin上的方法 } })
优点:
- 确实起到了重用代码的作用
缺点:
- 它是隐式依赖,隐式依赖被认为在React中是不好的
- 名字冲突问题
- 只能在ReactcreateClass里工作,不支持ES6的ClassComponent
- 实践下来发现:难以维护
.在React官网中已经被标记为不推荐使用,官方吐槽点这里。
2. HOC
2015年开始,React团队宣布不推荐使用Mixin,推荐大家使用 HOC 模式
.
HOC 采用了’装饰器模式’来复用代码:function withWindowWidth(BaseComponent) { class DerivedClass extends ReactComponent { state = { windowWidth: window.innerwidth, } onResize = () => { this.setState({ windowWidth: window.innerwidth, }) } componentDidMount() { window.addEventListener(resizethisonResize) } componentWillUnmount(){ window.removeEventListener(resizethisonResize) } render() {...} } }
经典的容器组件与展示组件分离(separation of container presidential) 就是从这里开始的。
.
下面是最最经典的 HOC 容器组件与展示组件分离 案例 - Redux 中的 connect 的实例代码:export const createInfoScreen = (ChildComponent, fetchData, dataName) => { class HOComponent extends Comnonent { state = { counter: 0 } handleIncrementCounter = () => { this.setState({ counter:this.state.counter + 1 }); } componentDidMount(){ this.props.fetchData(); } render() { const { data={},isFetching, error } = this.props[dataName]; if (isFetching) { return( <div>Loading</div> ); } if (error) { return( <div>Something is wrongPlease tryagain!</div> ... ) } } } }
优点
- 可以在任何组件包括 Class Component 中工作
- 它所倡导的容器组件与展示组件分离原则做到了:关注点分离
缺点
- 不直观,难以阅读
- 名字冲突
- 组件层层层层层层嵌套
3. Render Prop
2017 年开始,Render Prop 流行了起来。
.
Render Prop 采用了‘代理模式’ 来复用代码:class WindowWidth extends React.Component { propTypes = { children: PropTypes.func.isRequired } state = { windowWidth: window.innerWidth, } onResize = () => { this.setState({ windowWidth: window.innerWidth, }) } componentDidMount() { window.addEventListener('resize', this.onResize); } componentWillUnmount() { window.removeEventListener('resize', this.onResize); } ... }
React Router 也采用了这样的 API 设计:
<Route path = "/about" render= { (props) => <About {...props} />}>
优点:
- 灵活
缺点:
- 难以阅读,难以理解
4. Hook
2018 年,React团队宣布推出一种全新的重用代码的方式 —— React Hook。
.
它的核心改变是:允许函数式组件存储自己的状态,在这之前函数式组件是不能有自己的状态的。
.
这个改变使我们可以像抽象一个普通函数一样抽象 React 组件中的逻辑。
.
实现的原理:闭包import { useState, useEffect } from "react"; const useWindowsWidth = () => { const [isScreenSmall, setIsScreenSmall] = useState(false) let checkScreenize = () => { setIsScreenSmall(window.innerWidth < 600); }; useEffect(()=> { checkscreenSize(); window.addEventListener("resize", checkscreenSize); return () => window.removeEventListener("resize", checkScreenSize); }, []); return isScreenSmall }; export default useWindowsWidth
import React from 'react' import useWindowWidth from'./useWindowWidth.js' const MyComponent = () => { const onSmallScreen = useWindowWidth; return ( // Return some elements ) }
优点:
- 提取逻辑出来非常容易
- 非常易于组合
- 可读性非常强
- 没有名字冲突问题
缺点
- Hook 有自身的用法限制:只能在组件顶层使用,只能在组件中使用
- 由于原理为闭包,所以极少数情况下会出现难以理解的问题
reduxjs/redux-thunk: Thunk middleware for Redux
核心源码:
import type { Action, AnyAction } from 'redux'
import type { ThunkMiddleware } from './types'
export type {
ThunkAction,
ThunkDispatch,
ThunkActionDispatch,
ThunkMiddleware
} from './types'
/** A function that accepts a potential "extra argument" value to be injected later,
* and returns an instance of the thunk middleware that uses that value
*/
function createThunkMiddleware<
State = any,
BasicAction extends Action = AnyAction,
ExtraThunkArg = undefined
>(extraArgument?: ExtraThunkArg) {
// Standard Redux middleware definition pattern:
// See: https://redux.js.org/tutorials/fundamentals/part-4-store#writing-custom-middleware
const middleware: ThunkMiddleware<State, BasicAction, ExtraThunkArg> =
({ dispatch, getState }) =>
next =>
action => {
// The thunk middleware looks for any functions that were passed to `store.dispatch`.
// If this "action" is really a function, call it and return the result.
if (typeof action === 'function') {
// Inject the store's `dispatch` and `getState` methods, as well as any "extra arg"
return action(dispatch, getState, extraArgument)
}
// Otherwise, pass the action down the middleware chain as usual
return next(action)
}
return middleware
}
export const thunk = createThunkMiddleware()
// Export the factory function so users can create a customized version
// with whatever "extra arg" they want to inject into their thunks
export const withExtraArgument = createThunkMiddleware
部分引用笔记还在草稿阶段,敬请期待。。。