React工具

关于框架选择

Creat React App

它适用于以下类型的网站:
管理后台
仪表盘
数据分析
form 表单
内网应用

CRA 的优势:
✅ 官方出品
✅ 零配置
✅ CSR(即页面完全在浏览器渲染),简单易于学习
✅ 服务器和客户的代码完全解耦
✅ 易于部署,因为打包后的文件是静态文件
CRA 的劣势:
⛔️ 打包后的代码可能会臃肿
⛔️ 需要手动配置路由、状态管理、代码分割、样式文件等
⛔️ 不能用于需要 SEO 检索的网站
⛔️ 首屏效果不好,因为 CSR 页面在初始加载时比较慢,

Gatsby

它适用于以下类型的网站:
完美支持个人网站、博客、文档网站(PS: React 的官方文档使用的就是 Gatsby),甚至是电子商务网站。

Gatsby 的优势:

✅ 页面渲染性能优秀
✅ 对 SEO 友好
✅ 对打包文件进行了优化
✅ 轻松部署到 CDN(基于出色的扩展功能)
✅ 可以创建一个具有离线功能的 PWA 应用
✅ 丰富的插件系统

Gatsby 的劣势:

⛔️ 使用起来相较于 CRA 更为复杂
⛔️ 需要了解 GraphQL 和 Node.Js 的相关知识
⛔️ 配置繁重
⛔️ 构建时间会随着内容的增加而变长
⛔️ 有些功能可能需要付费

值得强调的是,丰富的插件系统是选择 Gatsby 的重要原因,比如 Gatsby 提供许多博客主题插件,其他例如谷歌分析、图片压缩、预加载插件等等。

Next.js

适用于高动态或者面向用户的网页,这些页面需要优秀的 SEO,并且可能每分每秒都在变化。
举个例子:今日头条的首页会根据每个人不同的喜好来推送不同的信息流。如果使用 Gatsby 或 Create React App,会首先渲染一个空页面,然后通过 HTTP 调用来获取信息流的新闻数据。然后有了 Next ,可以在服务器端进行数据的获取,并返回完整的页面。
可以在 Next.js 的展示页面 查看有哪些应用是用 Next.js 构建的。
Next.js 的优势:

✅ 支持服务器端预渲染
✅ 对 SEO 友好
✅ 零配置
✅ 适用于面向用户的高动态内容
✅ 还可以像 Gatsby 一样做 SSG ( Server Side Generation)

Next.js 的劣势:

⛔️ 使用起来比 CRA 更复杂
⛔️ SSR 增加了额外的复杂程度
⛔️ 扩展取依赖于服务器
⛔️ 没有丰富的插件生态系统
⛔️ 有些功能可能需要付费

不可变数据

既然是不可变数据,为何还能变更。这里说的不可变是指不能改变数据本身,如果需要改变数据,是先创建一个该数据的副本,然后在这个副本上进行修改操作,这样就在没有改变原始数据的基础上做了数据变更。此技术称之为写时复制。

1
2
3
4
5
6
7
8
9
10
11
12
const [todos, setTodos] = useState([
{
id: "1",
name: "learn immer",
},
]);

function addTodo(todo) {
const newTodos = [...todos, todo]; // 这里不是直接修改todos,而是创建了一个newTodos。

setTodos(newTodos);
}

immer

当拷贝一个值时,如果这个值是引用类型,那么直接赋值给另一个值时,会把值的引用也拷贝过去,改变这个值,会影响原值。
通常我们会使用深拷贝解决。
而 immer 会将改变应用到值的代理上,返回新的值。这样也不会影响原值。
immer 是使用 Proxy 进行的包装。

安装

1
yarn add immer

对比

1
2
3
4
5
6
7
8
9
10
11
12
//常规
const state = { id: "1", name: "张三" };
const newState = { ...state, name: "李四", age: 25 };
//immer
import produce from "immer";

const state = { id: "1", name: "张三" };

const newState = produce(state, (draft) => {
draft.name = "李四";
draft.age = 25;
});

更多层级

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
//常规
const state = {
id: "1",
name: "张三",
address: {
city: "北京",
},
};

const newState = {
...state,
address: {
...state.address,
city: "上海",
},
};
//immer
import produce from "immer";
const state = {
id: "1",
name: "张三",
address: {
city: "北京",
},
};

const newState = produce(state, (draft) => {
draft.address.city = "上海";
});

console.log(state);
/*输出:
{
id: '1',
name: '张三',
address: {
city: '北京',
},
}
*/
console.log(newState);
/*输出:
{
id: '1',
name: '张三',
address: {
city: '上海',
},
}
*/

更新数组

1
2
3
4
5
const nums = [1, 2, 3, 5];

const newNums = [...nums, 6]; // 在数组的末尾添加6
const newNums2 = [...nums.slice(0, 1), ...nums.slice(2)]; //删除数字2
const newNums3 = [...nums.slice(0, 1), 7, ...nums.slice(2)]; // 将数字2替换为7
1
2
3
4
5
6
7
8
9
10
11
12
import produce from "immer";
const nums = [1, 2, 3, 5];

const newNums = produce(nums, (draft) => {
draft.push(6);
}); // 在数组的末尾添加6
const newNums2 = produce(nums, (draft) => {
draft.splice(1, 1);
}); //删除数字2
const newNums3 = produce(nums, (draft) => {
draft.splice(1, 1, 7);
}); // 将数字2替换为7

对象数组操作

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
const todos = [
{
id: 1,
text: "learn react",
},
{
id: 2,
text: "learn redux",
},
{
id: 3,
text: "learn immer",
},
];

function addTodo(todo) {
return [...todos, todo];
}

function toggleTodo(index) {
return todos.map((todo, idx) => {
if (idx === index) {
return { ...todo, completed: !todo.completed };
}
return todo;
});
}
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
const todos = [
{
id: 1,
text: "learn react",
},
{
id: 2,
text: "learn redux",
},
{
id: 3,
text: "learn immer",
},
];

function addTodo(todo) {
return produce(todos, (draft) => {
draft.push(todo);
});
}

function toggleTodo(index) {
return produce(todos, (draft) => {
draft[index].completed = !draft[index].completed;
});
}

在底层,immer 使用了 ES6 Proxy 对现有的状态(即 JavaScript 对象)做了代理,形成了 draft 对象。对 draft 对象的任何同步赋值操作都会被代理捕捉到,等变更操作完成后,immer 会根据捕捉到的变更操作,来生成新的 JavaScript 对象。

与 React 的状态更新结合使用

使用 immer 可以简化深层的状态更新操作。如下所示:

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 { useState } from "React";
import produce from "immer";

const [user, setUser] = useState({
userName: "张三",
age: 16,
});

// 常规方式
const handleBirthDayClick = () => {
setUser((prevUser) => ({
...prevUser,
age: prevUser.age + 1,
}));
};

// immer方式
const handleBirthDayClick2 = () => {
setUser(
produce((draft) => {
draft.age += 1;
})
);
};

当然,如果真的遇到很深层次的 React 组件状态,就需要看看是否真的有必要设计出这么深层次的数据结构。

immer 更常见的是与 useReducer 结合使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { useReducer } from "React";
import produce from "immer";

const todoReducers = produce((draft, action) => {
switch (action.type) {
case "ADD_TODO":
draft.push(action.payload);
return;
case "TOGGLE_TODO":
draft[action.payload].completed = !draft[action.payload].completed;
return;
default:
}
});

const [todos, dispatch] = useReducer(todoReducers, []);

immutable.js

这是 Facebook 官方推出的数据不可变工具.

可变数据

引用类型就是可变数据,复制一份引用类型,改变内部属性的值,所有复制的值都会变化,因为引用的是同一份地址.

不可变数据方案

常规的比如浅拷贝,但是对于层级比较深的就无法处理.

Redux devtools

调试工具。

  1. 下载 redux devtools 插件。
1
2
3
4
5
6
7
8
9
10
// 开启 redux-devtools
const composeEnhancers =
(window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

// 使用中间件
const middleWares = applyMiddleware(thunk, reduxPromise);
// 配合 devtools
const enhancer = composeEnhancers(middleWares);
// 创建store
const store: Store = legacy_createStore(enhancer);

动效库 framer-motion

需要自定义配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<motion.div
variants={staggerTextContainer}
initial="initial"
whileInView="animate"
viewport={{ once: false, amount: 0.6 }}
className="flex flex-col lg:flex-row"
>
{/* text */}
<motion.div variants={fadeInUp} className="lg:w-[40%]">
<h3 className="h3">{pretitle}</h3>
<h2 className="h2 mb-6">{title}</h2>
</motion.div>
{/* slider */}
<motion.div variants={fadeInUp} className="lg:w-[60%] lg:absolute lg:right-0">
<Slider clients={clients} />
</motion.div>
</motion.div>