介绍

1
2
3
4
5
// 创建项目(官方 create-react-app 已废弃,但不影响学习)
npx create-react-app xxx

// JSX 是JS的语法扩展,浏览器不能识别,需要通过解析工具解析之后在浏览器中运行
// https://babeljs.io 编译参考

基础语法

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// 表达式通过大括号语法 {}
const message = 'this is message'
function getAge() { return 18 }
// .js 文件必须要默认导出一个组件
export default function App(){
return (
<div>
<h1>this is title</h1>
{/* 字符串识别 */}
{'this is str'}
{/* 变量识别 */}
{message}
{/* 函数调用 渲染为函数的返回值 */}
{getAge()}
</div>
)
}


// 列表
const list = [
{id:1001, name:'Vue'},
{id:1002, name: 'React'},
{id:1003, name: 'Angular'}
]

function App(){
return (
<ul>
{list.map(item=><li key={item.id}>{item}</li>)}
</ul>
)
}


// 条件渲染
const flag = true
const loading = false

function App(){
return (
<>
{flag && <span>this is span</span>}
{loading ? <span>loading...</span>:<span>this is span</span>}
</>
)
}

const type = 1 // 0|1
function getArticleJSX(){
if (type === 0) return <div>无图模式模版</div>
else (type === 3) return <div>三图模式模版</div>
}

function App() { return (<>{ getArticleJSX() }</>) }


// 事件绑定
function App(){
const clickHandler = (e, value)=> {
console.log('button按钮点击了')
console.log('值', e, value)
}
return (
<button onClick={clickHandler}>click me</button>
<button onClick={(e) => clickHandler(e, 1)}>click me</button>
)
}


// 组件
function Button(){
return <button>click me</button>
}

// 2. 使用组件
function App(){
return (
<div>
{/* 自闭和 */}
<Button/>
{/* 成对标签 */}
<Button></Button>
</div>
)
}


// 状态管理
function App() {
const [ count, setCount ] = useState(0)
return (
<div>
{/* setCount 里面别写 count++ 不修改源数据 */}
<button onClick={()=>setCount(count+1)}>{ count }</button>
</div>
)
}
// 初始状态需要经过复杂计算得到,传递函数(不支持异步)
const [num, setNum] = useState(() => {
const num1 = 1 + 2;
const num2 = 2 + 3;
return num1 + num2
});

// 注意:打印捕获不到,setTimeout 闭包,新的函数才会有
setCount(count + 1);
console.log(count);
setTimeout(() => {
console.log(count);
});
// 函数式可以拿到变更后的值,react 普通更新用旧值,函数式更新用最新值
setCount(count + 1); // 1
setCount(count + 1); // 1
setCount((prevCount) => {
return prevCount + 1; // 2
});

// 注意:自动依赖,也会有响应式,每次执行新的函数
let showCount = 0
if(count) {
showCount = count
}

// use-immer 库,简化更新
npm install immer use-immer
import { useImmer, useImmerReducer } from 'use-immer';
const [person1, setPerson1] = useState({
work: {
city: "a",
},
});
const [person2, updatePerson2] = useImmer({
work: {
city: "a",
},
});
// 更新 city
setPerson1({
...person1,
work: {
city: "b",
}
})
updatePerson2((draft) => {
draft.work.city = "b";
});
// 同理 useImmerReducer 也不需要返回,直接修改。
function reducer(draft, action) {
switch (action.type) {
case 'ADD_TODO':
// 直接 push,无需展开运算符
draft.todos.push({ id: Date.now(), text: action.text, done: false });
break;
case 'TOGGLE_TODO':
// 直接修改属性
const todo = draft.todos.find(t => t.id === action.id);
if (todo) todo.done = !todo.done;
break;
// 不需要 default return,Immer 自动处理
}
}
const [state, dispatch] = useImmerReducer(reducer, initialState);

// class 写法
return (<div className={xxx === xxx ? 'nav-item active' : 'nav-item'}></div>)
return (<div className={[xxx === xxx ? 'nav-item active' : '', 'nav-item'}].filter(Boolean).join(' '))}]></div>)
// class 可以使用 classNames 库实现
import classNames from 'classnames'
className={classNames('nav-item', { active: type === item.type })}

// style 写法
<div style={{color: 'red', fontSize: '16px'}}>xxx</div>
const styleObj = {
color: 'red',
'font-size': '16px'
}
<div style={styleObj}></div>

useRef 获取 DOM

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
function App(){
const inputRef = useRef(null)

const onChange = ()=>{
// inputRef.current 属性
console.log(inputRef.current, inputRef.current.value)
}

return (
<input
type="text"
ref={inputRef}
onChange={onChange}
/>
)
}


// useRef 的跨渲染周期保持、但变化不触发重渲染的数据
import { useRef } from 'react';
function MyComponent() {
// 创建一个 ref,初始值为 { count: 0 }
const instanceVar = useRef({ count: 0 });

const handleClick = () => {
// 修改 ref.current 不会触发重新渲染
instanceVar.current.count += 1;
console.log('Current count (no re-render):', instanceVar.current.count);
};

return <button onClick={handleClick}>Click me</button>;
}


组件基础

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// props 传递
// 可以传递 比如数字、字符串、布尔值、数组、对象、函数、JSX
function Son(props){
return <div>{ props.name }, {props.child}</div>
}
function App(){
const name = 'this is app name'
return (
<div>
<Son name={name} child={<span>test</span>}/>
</div>
)
}

// 特殊的 props(children)
// 当我们把内容嵌套在组件的标签内部时,组件会自动在名为 children 的 prop 属性
function Son(props) {
return <div> {props.children}</div>;
}
function App() {
return (
<div>
<Son>
<span>child1</span>
<span>child2</span>
</Son>
</div>
);
}

// 暴露子组件方法
// 在 react 19 中,forwardRef 该 API 已经被废弃了,可以将 ref 作为 props 进行传递。
import { useState, forwardRef, useRef, useImperativeHandle } from 'react';
const SubCom = forwardRef((props, ref) => {
const [val, setVal] = useState('x');
const onTest = () => {
console.log('onTest');
};
// 自定义需要暴露给父组件的属性或方法
useImperativeHandle(ref, () => ({
val,
onTest,
}));
return (
<div>
<input type="text" value={val} onChange={(e) => setVal(e.target.value)} />
</div>
);
});

const ForwardRef = () => {
const subComRef = useRef(null);

return (
<>
<div>ForwardRef</div>
<SubCom ref={subComRef} />
<button onClick={() => console.log(subComRef.current)}>LOG</button>
<button onClick={() => subComRef.current?.onTest()}>调用子组件方法 - onTest</button>
</>
);
}

// 跨层组件通信
// 使用 createContext 方法创建一个上下文对象 Ctx
// 在顶层组件(App)中通过 Ctx.Provider 组件提供数据
// 在底层组件(B)中通过 useContext 钩子函数获取消费数据
import { createContext, useContext } from "react"
// 1. createContext方法创建一个上下文对象
const MsgContext = createContext()
function A () {
return (
<div>
this is A component
<B />
</div>
)
}
function B () {
// 3. 在底层组件 通过useContext钩子函数使用数据
const msg = useContext(MsgContext)
return (
<div>
this is B compnent,{msg}
</div>
)
}
function App () {
const msg = 'this is app msg'
return (
<div>
{/* 2. 在顶层组件 通过Provider组件提供数据 */}
{/* 固定的 value 写法,多个值使用对象 */}
<MsgContext.Provider value={msg}>
this is App
<A />
</MsgContext.Provider>
</div>
)
}

// 其他使用方式
<MsgContext.Consumer>
{(value) => { return () }}
</MyContext.Consumer>

// 一次绑定多个值
<Son {...xxx} />

useEffect

介绍:用在 React 组件中创建不是由事件引起而是由渲染本身引起的操作(副作用), 比如发送请求,更改 DOM 等等。

  1. 参数1 是一个函数,可以把它叫做副作用函数,在函数内部可以放置要执行的操作
  2. 参数2 是一个数组(可选参),在数组里放置依赖项,不同依赖项会影响第一个参数函数的执行,当是一个空数组的时候,副作用函数只会在组件渲染完毕之后执行一次
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
// 基本使用,空数组第二次进来不会执行(关闭严格模式)
// 参数2 空数组依赖,只在初始渲染时执行一次
// 参数2 没有依赖项,组件初始渲染 + 组件更新时执行
// 参数2 添加特定依赖项,组件初始渲染 + 依赖项变化时执行

function App() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(1);
}, []);
return <div>Count: {count}</div>;
}
// 如果参数2 没有或者是 count 里面修改的会一直调用


// 清除副作用
// 函数最常见的执行时机是在组件卸载时自动执行
function Son () {
// 1. 渲染时开启一个定时器
useEffect(() => {
const timer = setInterval(() => {
console.log('定时器执行中...')
}, 1000)

return () => {
// 清除副作用(组件卸载时)
clearInterval(timer)
}
}, [])
return <div>this is son</div>
}

// 返回的函数还会在下一次 effect 执行前运行(如果依赖项发生变化)

useLayoutEffect(同步副作用)

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
// 在 DOM 变更之后、浏览器绘制之前,同步执行
/*
React 渲染 DOM
↓
执行 useLayoutEffect ← 立即同步执行
↓
浏览器绘制屏幕(已是最终效果)
↓
用户看到最终结果(无闪烁)
*/

// 使用场景
// DOM 测量
useLayoutEffect(() => {
const rect = ref.current.getBoundingClientRect();
setHeight(rect.height);
}, []);

// 焦点设置
useLayoutEffect(() => {
inputRef.current?.focus();
}, []);

// 动画初始化
useLayoutEffect(() => {
gsap.set(ref.current, initialState);
startAnimation();
}, []);

useReducer

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
const [state, dispatch] = useReducer(reducer, initialArg, init?)
/*
reducer 是一个处理函数,用于更新状态, reducer 里面包含了两个参数,第一个参数是 state,第二个参数是 action。reducer 会返回一个新的 state。
initialArg 是 state 的初始值。
init 是一个可选的函数,用于初始化 state,如果编写了 init 函数,则默认值使用 init 函数的返回值,否则使用 initialArg。
*/

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();
}
}

const App = () => {
const [state, dispatch] = useReducer(reducer, initialState);

return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</>
);
}
export default App;

// 使用初始函数的场景
// 不推荐:每次渲染都会重新执行 createInitialState
const [state, dispatch] = useReducer(reducer, createInitialState(props));
// 推荐:createInitialState 仅在首次渲染时执行,第二个参数必须传递,就是 createInitialState 参数
const [state, dispatch] = useReducer(reducer, props, createInitialState);

memo + useMemo + useCallback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 防止 props 没变时的重新渲染
const Child = React.memo(({ style, onClick }) => {
console.log('rendered'); // 只有 style 或 onClick 引用变化时才打印
return <div onClick={onClick} style={style}>子组件</div>;
});

// useCallback,不使用 useCallback 每次创建一个新的函数,组件也会重新渲染
// 配合 React.memo 使用
const handleClick = useCallback(() => {
console.log('click')
}, []); // 空依赖 = 引用永远不变
return <Child onClick={handleClick} />;

// useMemo 昂贵计算
const sortedList = useMemo(() => {
return heavySort(list, criteria);
}, [list, criteria]);

// 保持对象引用稳定(配合 memo 使用)
const style = useMemo(() => ({ fontSize: size }), [size]);
<Child style={style} label="size 改变才会渲染" />

自定义 Hook

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
function useToggle () {
// 可复用的逻辑代码
const [value, setValue] = useState(true)

const toggle = () => setValue(!value)

// 哪些状态和回调函数需要在其他组件中使用 return
return {
value,
toggle
}
}

function App () {
const { value, toggle } = useToggle()
return (
<div>
{value && <div>this is div</div>}
<button onClick={toggle}>toggle</button>
</div>
)
}

// 注意:react hook 使用限制
只能在组件中或者其他自定义 Hook 函数中调用
只能在组件的顶层调用,不能嵌套在 if、for、其它的函数中

Module CSS

1
2
3
4
5
6
7
8
9
10
11
// 嵌套写法
.father {
.app5Container {}
}
// 使用方式
<div className={styles.father}>
<div className={styles.app5Container}>生效</div>
</div>

// global 语法
:global(.father){ }

闭包陷阱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { useEffect, useState } from 'react';

function App() {
const [count,setCount] = useState(0);
/*
const updateCount = () => { setCount(count + 1) };
const ref = useRef(updateCount);
ref.current = updateCount;
*/
useEffect(() => {
setInterval(() => {
// 这个如果必须要用到 count,也就是闭包,useEffect 依赖项可以 [count],注意:定时器会多个,需要 return 清除
// 可以使用 useRef 来解决如上代码,setInterval(() => ref.current(), 1000)
console.log(count);

setCount(count + 1);
// 第一次引用了当时的 count 形成了闭包,每次拿到的都是 0
// setCount(count => count + 1); // 函数调用解决
// 或者使用 useReducer,每次调用 dispatch({ type: 'add', num: 1 })
}, 1000);
}, []);
return <div>{count}</div>
}
export default App;

Redux

1
2
// 参考:https://juejin.cn/post/7101688098781659172
@reduxjs/toolkit、react-redux

路由