基本概念
React
是一个用于构建用户界面的Javascript库,是DOM
的一个抽象层React
主要用于构建UI
状态管理
Props
props.children,类似于
vue
种的槽,只要组件内部有元素,那么它就是props.children,内部可以直接拿来用,例如:1
2
3
4
5
6
7
8
9
10
11<MyComponent>
<div>test</div>
</MyComponent>
function MyComponent(props) {
return (
<div>
{props.children}
</div>
)
}
Hooks
useEffect
副作用:数据获取、设置订阅、手动更改DOM,我们可以在函数组件中像类组件那样获取改变state
可以把
useEffect Hook
看作componentDidMount、componentDidUpdate、componentWillUnmount
这三个函数的组合,组件渲染完成后执行某些操作官方建议一个组件中不同的功能最好分开写
useEffect
默认
useEffect
每次重新渲染都会执行一次,可以传入第二个参数来控制渲染的次数,注意第二个参数最好不要穿入肯定不会变的复杂对象,例如函数等,否则肯可能造成每次都重新渲染:- 不传第二个参数,每次render都会执行
- 传入空数组,只会执行一次
- 传入一个值,当那个值改变的时候就执行
- 传入多个值,当其中某个值改变的时候就执行
1
useEffect(() => {}, [props.user]) // 这样当props.user改变的时候能够重新执行一次函数
副作用清除机制,在useEffect中返回一个函数,能够有效防止内存溢出等异常:
1
2
3
4
5
6
7
8
9
10
11
12useEffect(() => {
Client.subscribe(args, callBackFunc()); // 如果我们需要在渲染完成后进行订阅
return function cleanup () { // 如果需要在组件卸载的时候退出订阅就能这样做
Client.unsubscribe();
}
})
// interval或者timeout都是需要清理的,否则每次页面有个状态变更重新渲染的时候就会重新新建,造成内存泄漏
useEffect(() => {
const interval = setInterval(() => {}, 2000);
return () => clearInterval(interval);
})在函数组件中执行副作用操作,可以直接使用
state
,不用编写class
1 | import React, { useState, useEffect } from 'react'; |
useMemo/useCallback
两个比较类似,都是性能优化的手段,类似于类组件中的
shouldComponentUpdate
,在子组件中可以判断该组件的props和state是否有变化,避免服组件重新render的时候每次都重新渲染子组件useMemo返回一个值,避免在每次渲染时候都重新进行计算
1
2
3
4
5const data = {简单的计算过程} // 注意如果是简单的数据转换可以不用useEffect或者useMemo,直接这样即可
const data = useMemo(() => {复杂的计算过程}, [originalData]) // 这样除非originalData变了,否则父组件的改变不会引起子组件的变化
// 如果一个useEffect依赖于某个需要计算的值,那么这个值最好被useMemo包裹
useEffect(() => {doSomething()}, [data]) // 这里应该监听data而不是originalDatauseCallback返回一个函数,当把它返回的这个函数作为子组件使用时,可以避免每次父组件更新时都重新渲染子组件
1
const myButton = useCallback(<Button>{label}</Button>, [label])
Ref
1 | const ref = useRef(null) |
Context
- 和
Redux
类似,也有一个Provider
在最外面 - Context更适合存储全局的一些属性,例如用户选择的语言、地区偏好、UI主题等
- 还是觉得
Redux
方便好理解一点,而且功能强大一点,Redux
才是做了一个完整的状态管理功能,context
主要就是存储一个全局的数据,当然使用了context数据的的UI组件在context数据变化时也会刷新,是整个UI树的更新,用户体验和性能都会有问题 - 使用Context不用从最父级的组件一层一层往下传递了
1 | const WebContext: Context<boolean> = createContext<boolean>(true); |
路由Route
主要使用的是
React Router
,包括react-router
,react-router-dom
,react-router-native
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49import { Switch, Route } from 'react-router-dom'
// 检查是否登陆,如果未登陆就跳到登陆页
function CheckAuth({ Component }: any) {
const location = useLocation();
const auth = !!userStore.token; // your logic
const { pathname } = location; // 获取当前的路由pathname
if (auth && ['/sign', '/signup'].includes(pathname)) {
return <Navigate to="/dashboard" />;
}
if (!auth && !['/sign', '/signup'].includes(pathname)) {
return <Navigate to="/" />;
}
return <Component />;
}
// 路由定义
const Main = () => (
<main>
<Switch>
<Route path="/" element={<CheckAuth Component={SignIn} />} />
<Route path='/schedule' element={<CheckAuth Component={SignIn} />} />
<Route path='/about', render={() => <About something={this.something}>}/> <!--通过这种方式绑定数据或者方法给子路由-->
</Switch>
</main>
)
// 也可以用json的方式定义路由
const element = useRoutes([{
path: '/',
element: <Dashboard />,
children: [
{
path: 'message',
element: <DashboardMessages />
}
]
}])
this.props.location.pathname; // 获取当前的url路径
const params = useParams() // 获取路由参数
// 路由跳转
import { useNavigate } from "react-router-dom";
const navigate = useNavigate();
navigate('/about')需要注意的是,如果是
Link
链接的路由和当前路由是一样的,那么页面不会发生跳转,什么都不会做,这时候如果是弹出菜单,弹出菜单也不会自动关闭,所以这种情况可以单独处理一下,用a
标签代替一下,然后使用window.location.href来进行跳转吧,例如:1
2
3
4
5
6function jumpToMenu(url, e) {
e.preventDefault()
window.location.href = url
}
<a href={url} onClick={(e) => jumpToMenu(url, e)}>{text}</a> // 在onClick里面传递参数
组件
组件定义
函数组件
- 函数组件中能使用
useState
1 | // typescript中需要这样定义,FC是TypeScript使用的一个范性,意思是FunctionComponent |
组件属性
动态添加元素样式
style
1
<div style={{display: this.state.show ? "block" : "none"}}>动态样式</div>
动态添加类/条件渲染类
class
1
2<div className={this.state.show ? "show-class" : "hide-class"}>动态类</div>
<div className={style.style1 + ' ' + style.style2}>多个类</div>
常用组件
Prompt
- 在路由即将切换前弹出确认框(离开前确认)
- 需要注意的是,如果用户点击了取消,那么会组织路由的切换,但是用户的点击事件如果有监听,依然会触发点击事件的
- 最好在
message
里面判断是否需要展示Prompt
,不要在when
里面,因为在when
里面,每次重新render
都会执行,但是message
里面只是在用户真的打算跳转的时候才会执行
1 | <Prompt |
父子组件资源共享
父组件传递props给子组件,可以直接在父组件修改,会更新给子组件的
父组件将方法作为props传递给子组件
1 | // 父组件 |
JSX语法
基本语法
1 | <div tabIndex="0">ttt</div> // 可以使用双引号来直接指定属性值 |
代码片段fragments
- 为一个组件添加元素,并且不会在DOM中增加额外的节点,常规的做法是在外层包一个
div
,这样就会多一层DOM元素,为了减少元素数量,可以这样做
1 | return ( |
条件渲染
- 如果是在
JSX
外部的js
部分代码,那么直接使用js
自己的if
或者其他条件判断即可完成。在JSX
内部的话一般则是使用逻辑与&&
或者三目运算符完成。 - 如果条件渲染的结果全是字符串0,那么应该是条件没有转换为布尔值,可以使用双感叹号来转换,例如!!this.posts
- 如果需要用条件判断是否有子组件可以使用
React.isValidElement(children)
来判断
1 | render() { |
map循环
1 | <Nav> |
推荐三方组件
prop-types
- 类似于typescript,能够有效限制与定义组件中的prop参数类型
- 不过建议在pages页面(非component)中禁用eslint的
react/prop-types: 'off'
功能,因为页面的props可能太多,也和component中的有重复
1 | import PropTypes from 'prop-types'; |
react-animated-number
- 数字变化动态效果
- 虽然这个库很久没维护了,但是对比其他的库,这个库是我找到的唯一能满足我要求的
React Hook Form
- 表单hook
1 | const { register, setValue, getValues, trigger, handleSubmit, control, formState: { errors } } = useForm({ |
- toast提示框组件
React-Redux
- 从后端的角度看,就是一个维护全局变量的东西(也有同一个页面不同组件使用相同的东西,比如当前用户的用户名用户头像啥的)
- 个人现在都用mobx了,好配置得多
- Redux with typescript: Typescript里面如何定义,这里还有个example project
Action
定义了要发生什么,并且携带着数据(可以在action
里面调用API,将结果进行dispatch
,类似于vuex中的action
将结果进行mutation
),reducer
用来定义发生该事情后需要做什么(类似于vuex中的mutation),selector
可以理解是从state
获取数据的API。Redux
可以通过connect
方法,将store
的dispatch
方法保存到组件的props
中state
与props
的对应通常需要使用mapStateToProps
这个函数进行定义。它默认会订阅Store
,每当state
更新的时候,就会自动执行,重新计算UI组件的参数- 下面的方法在根组件外面包了一层
Provider
,这样所有的子组件默认都能拿到store
了 - 异步操作需要结合Thunk,太麻烦了
- 注意如果使用useselect等在第一次没有值,页面第二次渲染才有值,可能是因为没有将这些状态持久化造成的
1 | import { Provider } from 'react-redux' |
- 可以用
connect
将组件与store
进行连接,它可以接收四个参数:mapStateToProps(state, ownProps) : stateProps
,第一个参数就是Redus的store
,这个方法的返回值就会作为组件的props
Redux-Saga
从后端的角度看,就是在启动应用的时候,再启动一些单独的线程,这些线程可以异步去做些更改变量或者监听的事情,类似于钩子。
可以在这里对异步操作进行集中处理。
Effect
的几种方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import {take,call,put,select,fork,takeEvery,takeLatest} from 'redux-saga/effects'
// take: 用来监听action,返回监听到的action对象,例如如果有一个type=login的action,那么在执行dispatch(loginAction)之后,就可以这样获取对象
const action = yield take('login')
// call: 调用指定函数
yield call(myfunc, param1, param2);
// put: 触发dispatch,用于发送action,dispatch in saga
yield put({type: 'login'})
// select: 和getState类似,用于获取store中的state
const state = yield select()
// fork
// takeEvery/takeLatest: 用于监听简单的例子:
1
2
3
4
5
6
7
8
9
10
11// 在编写saga的文件里面一般这样写钩子函数
export function * mySaga(action) { // 这里的参数action,是包含了payload,state的对象
console.log("Hello Saga");
}
// 在main.js中,这样引入saga中间件
import createSagaMiddleware from 'redux-saga';
import {helloSaga} from './saga.js';
const sageMiddleware = createSagaMiddleware();
const store = createStore(rerducer, aplyMiddleware(sagaMiddleware));
sagaMiddleware.run(helloSaga);saga
中使用fetch
1
2
3function* onGetSuccess(state) {
const res = yield fetch("https://example.com").then(response => response.json());
}
Redux-actions
将redux-actions
和react-saga
配合使用可以简化大量的重复代码。在之前我们要创建一个action
需要线这样子定义:
1 | // 在使用redux-actions之前,需要这样创建一个action并使用 |
react-timeago
- 能够直接展示
1 hour ago / 2 days ago
等信息,不用去找啥时间组件了
Styled-Components
- 目的是将
React
组件包装成Styled
组件
1 | import { Card } from 'antd'; |
SWR
state-while-revalidate
的缩写,是HTTP RFC 5861
中描述的一种Cache-Control
扩展React
的Hook
组件,用于缓存从远端获取的数据- 常用语多次请求相同URL获取数据,或者更新数据后,先返回缓存的响应,与此同时去后台发送新的请求,以提高响应速度,减少等待时间
- 请求的结果根据标识
key
放在redux
里缓存,取数据的时候直接从redux
里面取 SWR
并不是一个请求库,只是一种管理数据的方式
1 | import useSWR from 'swr'; |
text-mask
- input mask用
事件
支持事件列表
1 | // 监听全局scroll事件 |
TroubleShooting
引入json格式的文件: 可以直接
import data from 'path/to/my.json'
React 表达式必须有一个父元素: 常出现在
JSX
的渲染的内嵌语句中返回了错误格式的结果:1
2
3
4
5
6
7
8
9render() {
return (
{
[ // 需要返回的应该是数组而不是混搭
<p>abc</p>,
<p>def</p>,
]
})
}Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef(): 可以尝试将函数组件修改为这样:
1
2
3export const MyComponent = React.forwardRef(({...props}: InputProps, ref) => {
...
});Property ‘ref’ does not exist on type ‘IntrinsicAttributes’: 可以尝试将组件用
forwardRef
封装一下
1 | import { forwardRef } from "react"; |
扩展阅读
- 在React中使用Redux
- React Icons库
- 在线视频播放库 react-player
- Victory: 比较原生的charts库,甚至可以用于
React-Native
- react-grid-layout: 可拖动的grid布局