1.React响应式原理
- 使用 diff 算法实现,React 在 state 或 props 改变时,会调用 render() 方法,生成一个虚拟 DOM 树,React 会将这棵树与上一次生成的树进行比较,找出其中的差异,并更新差异的部分
2.使用redux的步骤
-
思路:应用的整体全局状态以对象树的方式存放于单个 store。 唯一改变状态树(state tree)的方法是创建 action,一个描述发生了什么的对象,并将其 dispatch 给 store。 要指定状态树如何响应 action 来进行更新,你可以编写纯 reducer 函数,这些函数根据旧 state 和 action 来计算新 state
-
总结:redux通过dispacth调用action的方式通知store以何种方式更新状态
-
安装Redux Toolkit
-
# NPM npm install @reduxjs/toolkit # Yarn yarn add @reduxjs/toolkit
-
定义全局的store,一般是分不同的模块,最后挂载到index.js全局入口
-
import { configureStore } from '@reduxjs/toolkit'; import counterReducer from '../features/counter/counterSlice'; export const store = configureStore({ reducer: { counter: counterReducer, }, }); // import { Provider } from 'react-redux'; import { store } from './app/store'; root.render( <React.StrictMode> <Provider store={store}> <App /> </Provider> </React.StrictMode> );
-
通过redux/toolkit提供的createSlice同步方法定义子模块和createAsyncThunk异步方法定义子模块\
-
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; export const incrementAsync = createAsyncThunk( 'counter/fetchCount', async (amount) => { const response = await fetchCount(amount); // The value we return becomes the `fulfilled` action payload return response.data; } ); export const counterSlice = createSlice({ name: 'counter', initialState:{ value: 0, status: 'idle', }, reducers: { increment: (state) => { state.value += 1; }, decrement: (state) => { state.value -= 1; }, incrementByAmount: (state, action) => { state.value += action.payload; }, }, extraReducers: (builder) => { builder .addCase(incrementAsync.pending, (state) => { state.status = 'loading'; }) .addCase(incrementAsync.fulfilled, (state, action) => { state.status = 'idle'; state.value += action.payload; }); }, //导出子模块的actions export const { increment, decrement, incrementByAmount } = counterSlice.actions; //导出子模块的reducer export default counterSlice.reducer;
-
在需要使用仓库数据的js文件中使用
- 通过react-redux提供的useSelector调用仓库的变量
- 通过react-redux提供的useDispatch调用仓库的方法来修改变量
-
import { useSelector, useDispatch } from 'react-redux'; import { decrement, increment, incrementByAmount, incrementAsync, incrementIfOdd, selectCount, } from './counterSlice'; export function Counter() { const count = useSelector(selectCount); // const count = useSelector(state => state.counter.value); const dispatch = useDispatch(); const [incrementAmount, setIncrementAmount] = useState('2'); const incrementValue = Number(incrementAmount) || 0; return ( <div> <div className={styles.row}> {/* 加号和减号 */} <button className={styles.button} aria-label="Decrement value" onClick={() => dispatch(decrement())} > - </button> <span className={styles.value}>{count}</span> <button className={styles.button} aria-label="Increment value" onClick={() => dispatch(increment())} > + </button> </div> <div className={styles.row}> {/* 输入框 */} <input className={styles.textbox} aria-label="Set increment amount" value={incrementAmount} onChange={(e) => setIncrementAmount(e.target.value)} /> <button className={styles.button} onClick={() => dispatch(incrementByAmount(incrementValue))} > Add Amount </button> <button className={styles.asyncButton} onClick={() => dispatch(incrementAsync(incrementValue))} > Add Async </button> <button className={styles.button} onClick={() => dispatch(incrementIfOdd(incrementValue))} > Add If Odd </button> </div> </div> ); }
-
3.Hooks使用局限
- 只能在函数组件中使用
- 自定义 Hooks 命名规范:自定义 Hooks 必须以
use
开头,这是 React 对其的命名约定 - 可能导致性能问题:如果不小心使用过多的 Hooks 或不正确地使用它们,可能会导致性能问题。比如在 useEffect 中不正确地使用依赖数组,会导致重复渲染或内存泄漏。
- 没有生命周期方法
- 顺序和条件性调用:Hooks 必须按照规定的顺序调用,并且不能在循环、条件语句或嵌套函数中调用
4.React常用的hooks
-
useState
-
useEffect:用于在函数组件中执行副作用操作,它在组件渲染完成后执行,并可以在组件更新时根据指定的依赖项进行重新执行。
useEffect
接受两个参数:一个函数和一个可选的依赖项数组。函数参数表示需要执行的副作用操作,而依赖项数组表示需要监视的状态或变量。如果依赖项数组发生变化,React 将会重新执行副作用操作
5.type和interface的区别
- 总结:interface是接口声明,type是自定义类型声明,接口可能是上下文参数的声明,而类型可能是接口中某个对象的属性类型
- interface定义数据的具体数据结构如何,有哪些属性;type定义数据的具体值是什么类型
- interface可以被class继承和实现,也可以继承class;type不行
- interface不能作为交叉、联合类型的产物,而type没有限制
- 定义两个同名的type会报异常,而interface会合并
6.React响应式原理
- 使用 diff 算法实现,React 在 state 或 props 改变时,会调用 render() 方法,生成一个虚拟 DOM 树,React 会将这棵树与上一次生成的树进行比较,找出其中的差异,并更新差异的部分
7.TypeScript常用工具集
- Partial:可以将类型的所有属性都变为 可选 的
interface User {
name: string;
age: number;
}
type PartialUser = Partial<User>; // { name?: string; age?: number; }
- Required:可以将类型的所有属性都变为 必填 的(与 Partial 工具类型相反)
interface User {
name?: string;
age?: number;
}
type RequiredUser = Required<User>; // { name: string; age: number; }
- Readonly:可以将类型的所有属性设为 只读 ,这意味着属性不可以被重新赋值
interface User {
name: string;
age: number;
}
type ReadonlyUser = Readonly<User>;
- Pick:可以从类型中选取出指定的键值,然后组成一个新的类型
interface User {
name: string;
age: number;
email: string;
}
type BasicUserInfo = Pick<User, 'name' | 'age'>; // { name: string; age: number; }
- Omit:返回去除指定的键值之后返回的新类型(与 Pick 类型相反)
interface User {
name: string;
age: number;
email: string;
}
type UserInfoWithoutEmail = Omit<User, 'email'>; // { name: string; age: number; }
- Record:创建一个具有键类型和值类型的对象类型
type Weekday = 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday';
interface WorkSchedule {
[day: string]: string;
}
const schedule: Record<Weekday, string> = {
Monday: 'Work from home',
Tuesday: 'Client meeting',
Wednesday: 'Team lunch',
Thursday: 'Work on project',
Friday: 'Company event',
};
- Exclude:从联合类型中去除指定的类型
type AllowedColors = 'red' | 'green' | 'blue';
type PrimaryColors = 'red' | 'blue';
type NonPrimaryColors = Exclude<AllowedColors, PrimaryColors>; // 'green'
- Extract:从联合类型中提取指定的类型,类似于操作接口类型中的 Pick 类型(和Exclude相反)
type AllowedColors = 'red' | 'green' | 'blue';
type PrimaryColors = 'red' | 'blue';
type OnlyPrimaryColors = Extract<AllowedColors, PrimaryColors>; // 'red' | 'blue'
8. TypeScript
-
介绍
- TypeScript是JavaScript类型的超集,他可以编译成纯JavaScript代码
- TypeScript可以在任何浏览器、任何计算机和任何操作系统上运行,并且是开源的
-
编译代码
- tsc xxx.ts
-
类型注解
- TypeScript里的类型注解是一种轻量级的为函数或变量添加约束的方式
-
接口
- 在TypeScript里允许我们在实现接口时候只要保证包含了接口要求的结构,而不必明确地使用
implements
语句 -
interface Person { firstName: string; lastName: string; } function greeter(person: Person) { return "Hello, " + person.firstName + " " + person.lastName; } let user = { firstName: "Jane", lastName: "User" }; document.body.innerHTML = greeter(user)
- 在TypeScript里允许我们在实现接口时候只要保证包含了接口要求的结构,而不必明确地使用
-
类
- TypeScript支持JavaScript的新特性,比如支持基于类的面向对象编程。
-
class Student { fullName: string; constructor(public firstName, public middleInitial, public lastName) { this.fullName = firstName + " " + middleInitial + " " + lastName; } } interface Person { firstName: string; lastName: string; } function greeter(person : Person) { return "Hello, " + person.firstName + " " + person.lastName; } let user = new Student("Jane", "M.", "User"); document.body.innerHTML = greeter(user);
9.typescript元组
- 元组( Tuple )表示一个 已知数量 和 类型 的数组
- 元组用于表示一个结果,且有预定的长度,每一项可以是不同的类型
// 记录一个人的姓名、性别和年龄
let alex = ['Alex', 'male', 20];
let lucy = ['Lucy', 'female', 18];
// 我们在给元组某一项赋值的时候,只能赋值这一项的类型。比如,第 0 项是 string,那么我们就无法赋值为 number
alex[0] = 'Alexa';
// 如果我们想赋值一个下标超出了元组范围的话,某一项的话,那么这一项就可以既赋值为 string,又可以赋值为 number,但是无法赋值为其他类型
alex[3] = 3;
// 另外,我们直接给 alex 赋值的时候,不能够只赋值其中某一项,必须把这三项全部赋值给他才行,下面这样的话,就会报错
alex = ['Alex', 'male'];
10.useMemo和useCallback的区别
-
总结:
useMemo
和useCallback
都是用来优化性能的Hooks,useMemo
一般通过减少不必要的复杂计算来优化性能,useCallback
一般用于给子组件传递回调函数时,减少子组件的渲染次数,从而优化性能 -
useMemo(factory, Dependencies)
- 其中factory是工厂函数, Dependencies是其所有依赖值的数组
- 在组件初始化的时候会调用factory函数,并将返回值存储起来。当组件需要重新渲染时,react会首先依次判断每个依赖项的值是否发生了变化。如果没有变化,则直接使用之前的存储的返回值,而不会再次调用factory,如果依赖项发生变化,则会使用更新后的依赖项调用factory函数,返回最新的值
-
useCallback(callback, Dependencies)
- callback是一个函数对象,Dependencies是依赖项数组
- 默认情况下,每当一个组件中重新渲染,其中的函数都会重新声明。这样就会导致一个问题:如果该组件将这个函数传递给子组件,那么子组件也会重新渲染,但有时这样的渲染是不必要的
11.react组件通信
-
父传子
- 在父组件中的子组件标签上定义自定义属性,子组件中通过props接收
-
子传父
- 在父组件中的子组件标签上定义自定义属性和方法,子组件通过调用父组件方法进行通信
-
兄弟组件
- 将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
-
跨组件通信
- 导入并调用createContext方法,得到Context对象,导出
-
import { createContext } from 'react' export const Context = createContext()
- 使用 Provider 组件包裹根组件,并通过 value 属性提供要共享的数据
-
return ( <Context.Provider value={ 这里放要传递的数据 }> <根组件的内容/> </Provider> )
- 在任意后代组件中,如果希望获取公共数据: 导入useContext;调用useContext(第一步中导出的context) 得到value的值
-
import React, { useContext } from 'react' import { Context } from './index' const 函数组件 = () => { const 公共数据 = useContext(Context) return ( 函数组件的内容 ) }
12.react和vue的区别
-
核心思想不同:React推崇函数式编程(纯组件),数据不可变以及单向数据流,Vue则
灵活易用的渐进式框架,进行数据拦截/代理,它对侦测数据的变化更敏感、更精确
。 -
组件写法差异:React推荐的做法是
JSX
,也就是把 HTML 和 CSS 全都写进 JavaScript 中,Vue 推荐的做法是 template 的单文件组件格式,即 html,css,JS 写在同一个文件 -
diff算法不同:
-
总结:
- 传统Diff算法是循环递归每一个节点,算法复杂度为O(n*3)
- 采用虚拟DOM,算法复杂度为O(n)
- 不同的组件产生不同的DOM结构,type不同,就会直接删除旧DOM,创建新的DOM
- 同一层次的一组子节点,可以通过唯一的 key 区分
-
React的Diff算法核心实现
- react首先对新集合进行遍历
- 通过唯一key来判断老集合中是否存在相同的节点。如果没有的话创建,
- 如果有的话,会将节点在新集合中的位置和在老集合中lastIndex进行比较
- 如果渲染的节点位置小于老位置,进行移动操作,否则不进行移动操作
- 如果遍历的过程中,发现在新集合中没有,但在老集合中有的节点,会进行删除操作
-
Vue的Diff算法核心实现
旧children
和新children
各有两个头尾的变量StartIdx
和EndIdx
,它们的2个变量相互比较,一共有4种比较方式- 如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较的过程中,变量会往中间靠,一旦
StartIdx>EndIdx
表明旧children
和新children
至少有一个已经遍历完了,就会结束比较
-
-
响应式原理不同:
-
Vue
- Vue依赖收集,自动优化,数据可变,
- Vue递归监听data的所有属性,直接修改
- 当数据改变时,自动找到引用组件重新渲染
-
React
- React基于状态机,手动优化,数据不可变,需要
setState
驱动新的state替换老的state,数据改变时,React 中会需要shouldComponentUpdate
这个生命周期函数方法来进行控制
- React基于状态机,手动优化,数据不可变,需要
-