1.React优势
jsx语法(必须用babel转译)、单向数据流、虚拟dom、声明式设计 、组件化开发、官方说是UI库
2.State和props的异同
相同点:
都是js对象
都是保存信息的
都能触发渲染
区别:
props是父组件传入,state是自己组件内创建管理的。
props在子组件不可修改,state可以在组件内部修改。
3.react事件机制
在react的dom中绑定事件不是在原dom上绑定,太浪费性能。而是通过事件代理给根标签(#root)。
4.类组件的生命周期
创建阶段:
1. construct:实力过程中自动调用该方法,在方法内部通过super关键字 获取父组件的props,通常初始化state或在this上挂载方法。
2. getDerivedStateFromProps:组件创建和更新阶段,不论是props还是state更新都会执行,第一个参数为即将更新的props,第二个参数为上一个状态的state,可以通过比较两者加限制条件防止无用的state更新(返回null就是不对state进行操作,也可以返回新的state)。
3. render:类组件必须实现的方法,用于渲染dom结构,可以访问组件的state和props。不要再render中setState,否则会出发死循环更新组件导致内存崩溃。
4. componentDidMount:组件挂载到真实dom节点后执行,多用于异步获取数据,监听等。
更新阶段:
1. getDerivedStateFromProps
2. shouldComponentUpdate: 用于告知组件本身基于当前props和state是否需要重新渲染组件,默认返回true。执行实际:新的props和state都会调用。不建议再该方法中深层比较影响效率。不能调用setState,会导致无限循环内存崩溃。
3. render
4. getSnapshotBeforeUpdate:再render函数后执行,dom还没有被更新。该方法返回一个Snapshot值,作为componentDidUpdate的第三个参数传入。
5. componentDidUpdate:组件更新结束后。可以获取数据 更新dom样式等。
卸载阶段:
componentWillUnmount:组件卸载之前,清理事件监听,取消网络请求等。
5. setState执行机制,第二个参数
在组件生命周期或react合成事件中,是异步。
在setTimeout或原生dom事件中, 是同步。
第二个参数是函数:
该函数会在 setState 函数调用完成并且组件开始重渲染之后被调用,可以用该函数来监听渲染是否完成。相当于componentDidMount
this.setState(
{ username: 'tylermcginnis33' },
() => console.log( 'setState )
)
补充:哪些方法会触发react重新渲染以及如何避免不必要的重新渲染?
- 触发react重新渲染:会导致组件包括子组件全都重新渲染
setState() useState被调用。
props发生变化。
组件依赖的context发生变化。
自定义的hook发生变化。
forceUpdate强制组件更新。
- 如何避免:
React.memo:包裹函数组件,react渲染之前会对属性浅比较,如果没有变化重新渲染。第二个参数是一个可选函数,该函数接收新旧props两个值,返回false则更新组件,返回true不更新。
useMemo:缓存函数返回的一个结果。
useCallback:缓存函数。
key:key的作用增加复用。
React.PureComponent/shouldComponentUpdate:类组件的控制是否重新渲染组件。
6. React Hooks
6.1.useState
用于保存组件状态,并触发页面更新。每次执行setState() 方法,都会使得函数组件重新运行一遍
// 首先要引入useState
import react, { useState } from 'react'
export default function App () {
// useState要赋初值,并且返回一个数组,
// 该数组第一项name就是‘zhangsan’ ,
// 第二项setName 是一个函数,是改变前者的唯一方法。
const [ name, setName ] = useState('zhangsan')
return (
<div> app </div>
)
}
需要注意的是:使用setName()修改name的值,需要直接替换式写法:setName('lisi'), 'lisi'直接把‘zhangsan’替换了。如果是数组,不要用push直接追加,setList([...list, newDate]) 用这种方法。
6.2. useEffect - 副作用函数
作用:想要在函数组件里,某方法只运行一次,或监听某数据变化就执行,使用useEffect()
执行时机:整个页面渲染完(dom树和css树组合成的渲染树)才执行useEffect里面的代码。
6.2.1.useEffect如果只传第一个回调函数,不传第二个参数,则每次函数重新渲染都会执行。浪费性能
// 首先要引入useEffect
import react, { useState, useEffect } from 'react'
export default function App () {
const [ list, setList ] = useState([])
// 接收两个参数,第一个函数,App函数每次重新渲染都会执行axios
useEffect(()=>{
axios.get('api/123').then((res)=> {
setList(res.data.data)
})
})
return (
<div> app </div>
)
}
6.2.2 useEffect第二个参数为空数组,第一个回调函数只在组件第一次渲染时运行一次。
// 首先要引入useEffect
import react, { useState, useEffect } from 'react'
export default function App () {
const [ list, setList ] = useState([])
// 接收两个参数,第一个函数,第二个数组。
useEffect(()=>{
axios.get('api/123').then((res)=> {
setList(res.data.data)
})
},[])
return (
<div> app </div>
)
}
6.2.3 组件第一次运行,依赖变化再次执行。
// 首先要引入useEffect
import react, { useState, useEffect } from 'react'
export default function App () {
const [ name, setname ] = useState('zhangsan')
// 每次name变化,都会执行
useEffect(()=>{
setName(name.splice(0,1))
},[name])
return (
<div> app </div>
)
}
6.2.4 模拟组件销毁生命周期钩子
import react, { useState, useEffect } from 'react'
export default function App () {
const [ name, setname ] = useState('zhangsan')
useEffect(()=>{
const timer = setInterval(()=> {
console.log(1)
})
// return里的代码会在此组件销毁之前执行,注意第二参数数组必须为空。
return () => {
clearInterval(timer)
}
},[]) // 数组必须为空
return (
<div> app </div>
)
}
useEffect()不是生命周期!可以在函数组件中使用多次。
补充:useLayoutEffect() 是在dom树渲染完毕后立马执行,可以防止页面抖动,但影响加载速度。
重要补充:useEffect是怎么判断依赖项变化的?
使用了===全等运算符判断的,如果依赖项是数组或对象,即使值没有变化,引用地址也会变化,导致每次判定结果不一样引起第一个回调函数的执行。如果这些对象或者数组没有变化,但是组件重新渲染时会被重新创建,即使依赖项没有变化,useEffect也会重新执行。使用useMemo或useCallback避免。
6.3.useCallback()记忆函数 - 优化提升性能
useState也是记忆函数,因为每次使用useState中的set方法就会引起整个组件的重新执行(普通函数、普通变量都会被重新执行),但是记忆函数可以保存住原来更改过的值,不会导致每次都是初始值。
6.4.useMeno - 类似于vue计算属性
类似于计算属性,会返回一个结果。
import { useMemo } from 'react';
import { filterTodos } from './utils.js'
export default function TodoList({ todos, theme, tab }) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab] // 依赖项必须要
);
return (
<div className={theme}>
<p><b>Note: <code>filterTodos</code> is artificially slowed down!</b></p>
<ul>
{visibleTodos.map(todo => (
<li key={todo.id}>
{todo.completed ?
<s>{todo.text}</s> :
todo.text
}
</li>
))}
</ul>
</div>
);
}
6.5.useRef
功能一:保存变量
因为setState会重新更新整个组件,所以定义普通变量保存不住,每次拿到的都是初始化的值。
import { useRef } from 'react';
export default function Form() {
let count = useRef(0); // 传初始值
function handleClick() {
count.current++
}
return (
<>
<input ref={inputRef} />
<button onClick={handleClick}>
点我加1
</button>
</>
);
}
功能二:类似于React.ceateRef(),获取组件实例或dom元素
import { useRef } from 'react';
export default function Form() {
const inputRef = useRef(null); // 传null 或 什么都不传
function handleClick() {
inputRef.current.focus();
}
return (
<>
<input ref={inputRef} />
<button onClick={handleClick}>
聚焦输入框
</button>
</>
);
}
6.6.useContext - 优化 减少组件层级
优化context跨组件传值的复杂写法。
import React, { useContext } from 'react';
// 第一步:引用react的createContext函数,并确保跨组件传值使用的是同一个createContext()
const GlobalContext = React.createContext()
export default function GrandFather() {
const [name, setName] = setState('lisi)
return (
// 第二步 context的提供者标签,并传入value
<GlobalContext.Provider
value={{name:'zhansg', changeName:(value) => setName(value)}}
>
<Father/>
</GlobalContext.Provider>
);
}
export default function Father() {
return (
<Son/>
);
}
export default function Son() {
// 第三步:使用useContext函数包裹GlobalContext,然后直接使用。
const value = useContext(GlobalContext)
return (
<div onClick={()=>value.changeName(value.name)}>
value.name
</div>
);
}
6.7.useReducer - 常与useContext搭配使用
跨组件传值工具
import React, { useContext, useReducer } from 'react';
// 第二步:初始reducer的值
const initalValue = {
count:1,
}
// 第三步,接收两个参数,第一个是之前的值(老值),第二个是action收到dispatch的类型
const reducer = (pervState, action) => {
// 必须用新值代替旧值
const newValue = {...pervState}
switch(action.type) {
case 'plus':
newValue.count++
return newValue
case 'minus':
newValue.count--
return newValue
case 'a':
newValue.count = action.value
// 默认条件下返回原数据
default:
return pervState
}
}
// 第一步,useReducer接收两个参数,第一个是处理函数,第二个是初始值
// 返回一个数组,第一个值,第二个是更改reducer的dispatch方法。
const [state, disptch ] = useReducer(reducer, initalValue)
// 第四步:引用react的createContext函数,并确保跨组件传值使用的是同一个createContext()
const GlobalContext = React.createContext()
export default function GrandFather() {
const [name, setName] = setState('lisi)
return (
// 第五步 context的提供者标签,并传入value
<GlobalContext.Provider
value={state, dispatch} // 这里的state和dispatch是上面useReducer返回的。
>
<Father/>
</GlobalContext.Provider>
);
}
export default function Father() {
return (
<Son/>
);
}
export default function Son() {
// 第六步:使用useContext函数包裹GlobalContext。
const {state, dispath}= useContext(GlobalContext)
return (
// dispatch的语法 dispatch({type:'',value:data})
<div onClick={()=>dispatch({type: 'a'})}>
state.count
</div>
);
}
6.8.自定义Hooks
就是对逻辑的抽离,便于维护和理解。公共的逻辑可以复用。
优点:可以使用react的一些方法比如useState useEffect等。
通常会有return。
7. React + TS
7.1.类组件中使用ts
import React,{Component} from "react";
interface IProps {
name: string,
}
interface IState {
age: nunber,
}
// React的 Component组件可以接受两个泛型,第一个是props类型,第二个是state类型
export default class Hello extends Component<IProps,IState>{
state = { age: 18 }
render(){
return(
<div>
{this.props.name} - {this.state.age}
</div>
)
}
}
7.2.函数组件使用ts
import React from 'react'
interface IProps {
name: string,
cb?: () => void,
}
// 重点是这个React.FC<T> T是props类型
const App:React.FC<IProps> = (props) => {
const [ name, setName ] = useState<string>('zhangsan')
return (
<div>{props.name}</div>
)
}
8.react-router @6
8.1 一级路由与多级路由
8.2 路由重定向
8.2.1 重定向方法一:
注意 <Navigate> 组件是从react-router中引入的。
8.2.2 重定向方法二:
自定义Redirect组件
import { useEffect } from 'react'
import { Route, useNavigate } from 'react-router'
8.2.3 404页面
path="*" 就是匹配路径和所有定义的不同时,进入的404组件
8.3 路由嵌套
8.4 声明式导航与编程式导航
query传参:
8.5 动态路由
import { Route, useNavigate, useParams } from 'ract-router'
const navigate = useNavigate()
8.6 路由拦截
用到了react插槽概念,即插槽的组件在 props的children属性中。
8.7 路由模式
8.8 路由懒加载
用到了react的lazy方法和Suspense标签。
8.9 useRoutes钩子配置
类似于vue-router的配置方法配置路由:
useRoutes钩子直接返回一个组件,我们可以直接在app组件中使用该组件
import element from '@/route/index'
function App () {
return element
}
9.react-redux@7
9.1 redux三大原则
1. 单一数据源。
2. state是只读的。
3. 使用纯函数来执行修改。
9.2 react-redux使用流程
9.2.1 redux流程:- flux运行原理
描述:
在react组件中通过dispath方法传一个类型type和数据,store也就是redux接收之后,触发reducer中对应type的action,reducer第一个参数是previousState且必须初始化赋值,第二个参数就是action,在此reducer 中必须返回一个新的value。
9.2.2 总流程:
1.创建reducer
2. 将多个reducer合并起来:combineReducers 方法
3. 使用redux的 createStore方法创建store,applyMiddleware方法使用中间件
redux中间件有:redux-thunk,让redux可以使用异步调接口。redux-promise,相同。
4. 创建actions
action必须是一个函数,返回一个函数(高阶函数),上面是同步和异步的写法。
5. 使用react-redux的Provider标签包裹整个项目。并传入store
6.使用:
方式一:connet(mapStateToProps,mapDispatchToProps)(组件名)写法。
mapStateToProps:映射store的state 到组件的props中。
mapDispatchToProps: 映射dispath(action) 到组件的props中。
这样 store的state和dispath就可以从props中获取了。
方式二:使用react-redux的hooks。
获取store的state的hooks:import { useSelector } from 'react-redux'
获取store的dispath的hooks:import { useDispatch } from 'react-redux'
9.2.3 redux-toolkit包,更像是vuex和pinia。
此包时redux为我们自动安装thunk logger devTool中间件,不需要我们手动安装了。
优点:相对于传统方法,toolkit可以让我们在reducer中直接修改state,传统方法中state是不可变的只能用新值代替旧值。
具体使用:
第一步:创建slice,类似于vuex的各个模块。引入createSlice函数
// 'store/features/counterSlice'
import { createSlice } from '@reduxjs/toolkit';
export const counterSlice = createSlice({
name: 'counter', // 命名空间,在调用action的时候会默认的设置为action的前缀
// 初始值
initialState: {
count: 1,
title: 'redux toolkit pre',
},
// 这里的属性会自动的导出为actions,在组件中可以直接通过dispatch进行触发
reducers: {
increment(state, { payload }) {
// console.log(action);
state.count = state.count + payload.step; // 内置了immutable
},
decrement(state) {
state.count -= 1;
},
},
});
// 导出actions
export const { increment, decrement } = counterSlice.actions;
// 内置了thunk插件,可以直接处理异步请求
export const asyncIncrement = (payload) => (dispatch) => {
setTimeout(() => {
dispatch(increment(payload));
}, 2000);
};
export default counterSlice.reducer; // 导出reducer,在创建store时使用到
第二部:创建store:使用工具包中的configureStore函数
// 'store/index.ts'
import { configureStore } from '@reduxjs/toolkit';
import counterSlice from './features/counterSlice';
import movieSlice from './features/movieSlice';
// configureStore创建一个redux数据
export default configureStore({
reducer: {
counter: counterSlice,
movie: movieSlice,
},
});
第三步:在入口文件index.ts中引用store
import React from 'react';
import ReactDOM from 'react-dom';
// 引入Provider
import { Provider } from 'react-redux';
import './index.css';
import App from './App';
// 引入创建的store
import store from './store';
ReactDOM.render(
// 关键:
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
第四步:在组件中使用:
// 'src/app.tsx'
import { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, asyncIncrement } from './store/features/counterSlice'; // 引入actions
function App() {
// 使用useSelector获取store的state
const { count } = useSelector((state) => state.counter);
// 使用useDispath 调用reducer中的 action
const dispatch = useDispatch();
return (
<div className='App'>
<button
onClick={() => {
dispatch(increment({ step: 2 })); // dispatch派发action
}}
>
{count}
</button>
<hr />
<button
onClick={() => {
dispatch(asyncIncrement({ step: 1 }));
}}
>
{count}
</button>
</div>
);
}
export default App;
10.react性能优化总结
避免不必要的 render - 避免每次更新state props重新渲染dom、创建函数或对象。
- 生命周期render shouldComponentUpdate() useEffect 中禁止setState或useState
- 避免使用内联函数,建议使用函数定义与箭头函数。
- 使用react Fragment 减少标签
- 路由懒加载 (React.lazy与React.Suspense fallback)
- 使用useCallback useMemo React.Memo优化性能。