关于框架选择
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];
setTodos(newTodos); }
|
immer
当拷贝一个值时,如果这个值是引用类型,那么直接赋值给另一个值时,会把值的引用也拷贝过去,改变这个值,会影响原值。
通常我们会使用深拷贝解决。
而 immer 会将改变应用到值的代理上,返回新的值。这样也不会影响原值。
immer 是使用 Proxy 进行的包装。
安装
对比
1 2 3 4 5 6 7 8 9 10 11 12
| const state = { id: "1", name: "张三" }; const newState = { ...state, name: "李四", age: 25 };
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: "上海", }, };
import produce from "immer"; const state = { id: "1", name: "张三", address: { city: "北京", }, };
const newState = produce(state, (draft) => { draft.address.city = "上海"; });
console.log(state);
console.log(newState);
|
更新数组
1 2 3 4 5
| const nums = [1, 2, 3, 5];
const newNums = [...nums, 6]; const newNums2 = [...nums.slice(0, 1), ...nums.slice(2)]; const newNums3 = [...nums.slice(0, 1), 7, ...nums.slice(2)];
|
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); }); const newNums2 = produce(nums, (draft) => { draft.splice(1, 1); }); const newNums3 = produce(nums, (draft) => { draft.splice(1, 1, 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, })); };
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 2 3 4 5 6 7 8 9 10
| const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const middleWares = applyMiddleware(thunk, reduxPromise);
const enhancer = composeEnhancers(middleWares);
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" > {} <motion.div variants={fadeInUp} className="lg:w-[40%]"> <h3 className="h3">{pretitle}</h3> <h2 className="h2 mb-6">{title}</h2> </motion.div> {} <motion.div variants={fadeInUp} className="lg:w-[60%] lg:absolute lg:right-0"> <Slider clients={clients} /> </motion.div> </motion.div>
|