由 vue3 转学 react,之前从未接触过 react,自学了一遍感觉还好,容易上手。
期间的一些笔记,后续有更深的见解持续更新。

组件(类组件,函数组件)

  • 组件名称首字母要大写,必须有返回值,没有写null
  • 设置默认值:function 组件名({默认值:1})/ 类组件:static defaultProps ={ 默认值:1 }
  • 实例化:类组件有实例化,函数组件没有

组件通信

父传子

  1. 父组件通过 state 传递值。state 里设置要传递的值
  2. 给子组件标签添加属性,msg={this.state.msg1}
  3. 子组件通过 props 接受父组件传来的数据(类组件用this.props /函数组件通过参数)获取 ->props对象

子传父

​ 子组件调用父组件传递过来的函数,并且把想要传递的数据当成函数实参传入即可

兄弟组件通信

​ 利用共同的父组件,实现子传父,父在传子的兄弟通信

跨组件(祖孙)通信 Context

​ 在嵌套组件树里,采用 Context 不需要通过层层 props 传递,就能进行数据传递。

  1. 创建 Context 对象,导出 Provider 和 Consumer 对象
  2. 使用 Provider 包含根组件提供数据
  3. 将需要用到组件,使用 Consumer 包裹,然后 value => 获取
1
2
3
4
5
6
7
8
9
10
//祖组件
const { Provider,Consumer } = createContext()
<Provider value={this.state.nextMsg}>
<Brother/>
</Provider>

//孙组件
<Consumer>
{value => <span>{value}</span>}
</Consumer>

child 属性:在组件内部使用都会自带这一属性,可以是文本,标签元素,函数,jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//例如父组件
;<NextFather>
<div>
<p>标签</p>
{() => {
this.fun(id)
}}
</div>
</NextFather>

//NextFather子组件
function NextFather({ children }) {
return <div>{children}</div>
}

生命周期

在这里插入图片描述

挂载阶段:

  1. constructor(初始化 state,创建 Ref,使用 bind 解决 this 问题) ,初始化最先执行,只执行一次。

  2. render (渲染 UI),每次组件渲染的时候都会触发。

  3. componentDidMount(发送网络请求,Dom 操作),完成渲染后执行,初始化的时候执行一次。

更新阶段:

  1. render(与挂载相同,是同一个 render),每次组件都会触发
  2. componentDidUpdate(Dom 操作,可以获取到更新后的 Dom,不要直接调用 setState),更新后渲染

卸载阶段

  1. componentWillUnmount(执行清理工作,比如清理定时器),组件卸载

hooks 的一些 API

本质:让函数组件更强大更灵活的钩子,只能在函数组件(有状态)中使用。

作用:组件状态复用,class 组件自身问题

注意:只能写在函数组件最外层,不能写在 if,for 循环判断里面(官文文档解释为 react 的运行机制要保持这些 hooks 的顺序的唯一性),可以将回调函数作为参数传递

useState

const [count, setCount] = useState(initCount);

useState 传入的是当前 count 初始值也就是 initCount(可以是初始值也可以是函数),然后返回的是最新的 count,和一个修改 count 的方法(可以普通调用和函数调用,,数据是对象类型的话,一般要结合拓展运算符复制一份)。

1
2
3
4
5
6
7
8
9
//三种情况
setCount(count + 1)
setCount(() => count + 1)
setCount(() => {
return {
...count,
//再对象赋值
}
})

初始值 initCount 只有在首次渲染生效,后续更新会被忽略,直接调用 setCount

一句话:在同步里,异步更新状态和 dom,在异步里,同步更新状态和 dom。

useEffect:为更好处理副作用(除了更新 UI 外的作用,如 ajax,本地存储)

感觉像 vue 的 nextTick。

React 会在每次渲染完后调用 useEffect,包括第一次加载渲染 DOM。

useEffect 设计初衷是用来取代 componentDidMount 和 componentDidUpdate,它接收两个参数(fun,[]),一个处理函数,另一个关联的状态或数组,这个变了就重新执行。

加 [ ] 为行业只执行义一次的默认写法,一般用于发送请求:

1
2
3
4
5
6
7
8
useEffect(() => {
async function sendData() {
const res = await fetch('http://www.baidu.com')
.then((response) => response.json())
.then((data) => console.log(data))
}
sendData()
}, [])

useLayoutEffect 的作用和 useEffect 几乎差不多,几乎看不到任何差别,但它们的渲染底层逻辑就稍微不同。

具体可见这篇文章,用一个例子很清楚讲明,在性能优化上尽量选用 useEffect,在解决页面渲染更新会闪烁可以用 useLayoutEffect。

useRef:获取实例 dom 或组件方法,必须是类组件

步骤:导入 useRef 函数,通过 ref 绑定要获取的元素,执行 useRef 并传入 null,这将会返回一个含有current属性的对象。

1
2
3
4
5
6
7
8
9
10
11
12
import React, { useRef, useEffect } from 'react'
//app组件里绑定
<UseRef1 ref={hook} />
<p ref={pRef}>useRef学习,标签实例</p>

//获取
const pRef = useRef(null)
const hook = useRef(null)
useEffect(() => {
console.log(pRef.current)
console.log(hook.current.state.name)
}, [])

useContext:祖孙组件响应式更新

一个例子直接说明梗概。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React, { createContext, useContext, useState } from 'react'

//1.创建上下文
const Context = createContext()

//2.在顶层app组件上,使用Providr包裹孙组件,value传值
const [count,setCount]=useState(10)
<Context.Provide value={count}>
<div>
<Son />
<button onClick={()=>{setCount(count+1)}}></button>
</div>
</Context.Provider>

//3.在son组件里调用useContext方法,Context和createContext保存的变量一致
const count = useContext(Context)
//这样孙组件就可以显示或同步app组件的值

useReducer

const [ state,dispatch ] = useReducer(reducer, initState, init)

接收三个参数,分别为:处理状态更新的 reducer,状态初始值,状态初始化函数。

有两种传递方式:不传第三个参数,如 1;state 数据比较复杂,可以将第二个参数作为第三个参数传入,如 2。

最后,useReducer 将返回两个东西,一个当前最新的状态 state,一个是更新状态的 dispatch。

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
//1.将状态初始值作为第二个参数传入
const [state, dispatch] = useReducer(
reducer,
{count: initState}
)
//2.惰性初始化,将第二个参数作为第三个函数传入
function init(initState){
return {count: initState}
}

function renducer(state,action){
switch{
case "change":
return {count: state.count - 1}
case "reset":
return init(ac)
//...
}
}

function Counter({initState}){
const [ state,dispatch ] = useReducer(reducer, initState, init)
return(
<>
<button onClick={() => dispath({type: 'change',count: initState})}></button>
</>
)
}

useCallback

入参和 useEffect,接收两个参数(fun,[]),一个处理函数,另一个关联的状态或数组,这个变了就重新执行。

那为啥要用 useCallback:那是因为在函数组件里的函数,会随着状态值的更新而重新渲染,函数也会频繁被定义且组件通信很耗性能。使用useCallback+memo可以解决上述问题

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

useMemo(相当于 vue 的 computed)

入参和 useEffect,接收两个参数(fun,[]),一个处理函数,另一个关联的状态或数组,这个变了就重新执行。如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。

官方建议:不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo

useCallback + useMemo

痛点见上述

1
2
3
4
5
6
7
8
//使用Memo后确实不会影响了,但父组件传值过来呢
const Son = React.memo(function Son(){
...
})

//父组件:这样就不会
const childClick = useCallback(() => {}.[])
<Son click={childClick} />

路由:react-router-dom

先下载react-router-dom@6依赖,在导入import { BrowserRouter, Routes, Route, Link } from 'react-router-dom' , 基本使用如下。BrowserRouter:包裹整个路由,一个 react 应用只需使用一次,还有个路由模式:hashRouter

1
2
3
4
5
6
7
8
9
<BrowserRouter>
<Link to="/">index</Link>
<p></p>
<Link to="about">about</Link>
<Routes>
<Route path="/" element={<Index />}></Route>
<Route path="about" element={<About />}></Route>
</Routes>
</BrowserRouter>

编程式导航:replace 设置为 true 表示不保留历史记录

1
2
3
4
5
6
7
import { useNavigate } from 'react-router'
const navigage = useNavigate()
const goAbout = () => {
navigage('/about', { replace: true })
}

;<button onClick={goAbout}>跳转到关于我</button>

编程式导航传参方式:searchParams / params

1
2
3
4
5
6
7
8
9
//searchParams 传参:和取参
navigage('/about?id=0001', { replace: true })
let params = useSearchParams()
let id = params.get('id')

// params 传参:和取参
navigage('/about/id/0001', { replace: true })
let params = useParams()
let id = params.id

二级路由:如果要设置一级路由默认渲染的二级路由,可如下例所示

1
2
3
4
5
6
7
8
9
10
//给URL地址about后面添加nextRouter二级路由
//在About组件内引入Outlet,再设置<Outlet />出口即可
<Route path="about" element={<About />}>
{/* 二级路由 */}
<Route path="nextRouter" element={<NextRouter />}></Route>
{/* 默认二级路由 */}
<Route index element={<Board />}></Route>
{/* 404页面 */}
<Route path="*" element={<NotFound />}></Route>
</Route>

mobx

在这里插入图片描述

校验规则第三方插件: prop-type

标签传递 Boolean 值需要按对象形式传递,如 isTrue= {false},而非 isTrue= “false”

可以传递任何数据,包括函数,jsx

给组件添加内置属性或方法

1
2
3
4
5
//添加属性验证
Module.protoTypes={
title:Name.string
isTrue:flag.bool
}

添加校验规则:需要另外安装第三方插件npm install prop-type ,导入prop-types包,在组件名.propTypes={} 添加校验规则。

可检验的规则有:

  • 常见类型 array、bool、fun、number、obj、string
  • 基本类型:element
  • 必填项:isRequired
  • 特定结构对象:shape({})
1
2
3
4
5
6
7
import Proptypes from 'prop-types'
//父组件调用
<Son list={9}>
//子组件
Son.propTypes={
list:PropTypes.arr
}

其他

获取输入框 value 值的几种方法

  1. 通过在state 中定义变量,在输入框绑定value={this.state.component} ,和 onChange={this.changeInputValue} 事件,通过事件修改 state 的中变量。
1
2
3
4
5
changeInputValue = (e) => {
this.setState({
component: e.target.value,
})
}
  1. 通过 ref 拿到。先声明myRef = React.createRef(),在 input 标签内绑定ref={this.myRef},最后通过this.myRef.current.value 拿到 value 值。

修改数组的某项状态

删除:直接 filter 过滤传过来的值不等于数组的值就好,或者根据索引 splice(index, 1)item 项

1
this.setState({ list: this.state.list.filter((item) => item.id !== id) })

修改:一般对整个数组 map 遍历到需要修改的项

1
2
3
4
5
6
7
8
9
list: this.state.list.map((item) => {
if (item.id === id) {
return {
attitude: attitude === 1 ? 0 : 1,
}
} else {
return item
}
}),