# react-book **Repository Path**: react-stack/react-book ## Basic Information - **Project Name**: react-book - **Description**: react基本语法及进阶 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-10-23 - **Last Updated**: 2023-11-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ![](https://static.web.sdo.com/xcb/pic/xcb_act/20231027_NewArea/react-logo.jpg) ## React基础 ### 初识react: #### 01 react特点: react框架具有以下特点: - 声明式编码; - 组件化编码; - 使用visual DOM,不总是直接操作页面上的真实DOM; - DOM diffing算法,最小化页面重绘; #### 02 虚拟DOM: 虚拟DOM本质是一个object对象。虚拟DOM最终会被React转换成真实DOM,展示在页面上。 ```html ``` 来一段hello world的代码 ```react const Title =

这里是是一段虚拟DOM

; ReactDOM.render(Title, document.getElementById('app')); ``` 虚拟DOM的创建可以直接创建,也可以使用`React.createElement`进行创建。 ```react const App=React.createElement('h1',{className:'text-danger'},'APPLICATION'); ReactDOM.render(App,document.getElementById('app')) ``` ### JSX的使用: #### 01 jsx的规则: **JSX的规则:** - 定义虚拟DOM时,不要写引号; - 样式类名使用className代替class; - JSX中混入的js语句使用`{}`包裹; - 内联样式要写成对象键值对的形式; - 虚拟DOM只能有一个根标签; - 虚拟DOM中的标签必须闭合; - 只能写js表达式,不能写js语句**【if判断、for循环之类】**; - JSX中的列表渲染,每一项必须有唯一的Key值。 #### 02 样式定义: 样式可以使用类名和内联样式定义,类名可以定义成变量。 ```react const container = 'container'; class App extends React.Component { render() { return (

这里是标题

) } } ``` 样式也可以使用style内联样式定义: ```react class App extends React.Component { render() { return (

这里是标题

) } } ``` #### 03 数据填充: 在jsx中,数据填充使用**`{}`**包裹。 ```react const btnTitle = "增加"; class App extends React.Component { render() { return (
) } } ``` #### 04 列表渲染: 列表渲染一般使用map遍历,每一个循环体中要使用`key`进行标记。 ```react const list = [ { id: 1, name: 'react' }, { id: 2, name: 'vue' }, { id: 3, name: 'angular' } ]; class App extends React.Component { render() { return (
列表渲染
) } } ``` ### 三大核心之state: #### 01 state是什么: state是组件中控制状态的参数,其实质是一个对象,初始化时使用直接赋值,但是更新状态时,需要调用内置方法`setState`方法,并且更新状态是合并操作而不是替换操作。 state一般是定义在组件类中的构造函数中,当然也可以直接定义在构造器之外。 ```react // 定义在构造函数中 class App extends React.Component { constructor(props) { super(props); this.state = { counts: 0 } } render() { return (
当前值为:{this.state.counts}
) } } ``` state值也可以定义在构造函数外部。 ```react class App extends React.Component { state = { counts: 0 } render() { return (
当前值为:{this.state.counts}
) } } ``` #### 02 state的更改: state的更改使用**`setState`**方法,setState可以直接更改对象值,也可以使用回调函数进行状态的更改。 - **setState( {...}, callback ):** 这种方式需要提前获取当前state的值,然后再修改更新最新的值,回调函数是组件render之后调用,在其中可以获取到更新之后的state值。 ```react this.setState({ counts: this.state.counts + 1 }) ``` - **setState( fn ):** setState也可以使用函数式更改state的值,回调函数中可以获取到当前state值和props值。 ```react this.setState((state, props) => { return ({ counts: state.counts + 1 }) }) // 简写为 this.setState(state => ({ counts: state.counts + 1 })) ``` #### 03 setState的异步性: setState函数式异步的,与setState同步的代码获取的state值是没有更新后的值,只有在setState回调函数中才会获得最新的state的值。 ```react increment = () => { this.setState({ counts: this.state.counts + 1 }, () => { // 获取的是更新后的state的值 console.log("回调函数内:", this.state.counts) }) // 获取的是未更新的state值 console.log("事件触发后:", this.state.counts) } ``` ### 三大核心之props: #### 01 props的传值: props是react中父子组件相互传递值的媒介,默认传递的值为字符串。 ```react class Alert extends React.Component { render() { return (
{this.props.title}
) } } class App extends React.Component { render() { return (
) } } ``` #### 02 props约束传值: 如果需要规定prop的类型和默认值,需要借助于`prop-types`来定义。 ```react class Alert extends React.Component { render() { return (
{this.props.title}
) } } // 约束Alert组件的props类型 Alert.propTypes = { type: PropTypes.string.isRequired, title: PropTypes.string.isRequired } // 定义Alert组件的props默认值 Alert.defaultProps = { type: 'danger', title: '默认是一段警示文本' } ``` 借助于类的static关键字,我们可以将props约束定义成静态属性。 ```react class Alert extends React.Component { static propTypes = { type: PropTypes.string.isRequired, title: PropTypes.string.isRequired } static defaultProps = { type: 'danger', title: '默认是一段警示文本' } render() { return (
{this.props.title}
) } } ``` #### 03 构造器中的props: 对于类式组件,如果类中定义了构造函数,我们要将props通过super关键词进行传递,这样才能确保在构造器函数中正确的获得到props的值。 ```react class Alert extends React.Component { constructor(props) { super(props); console.log(this.props) } render() { return (
{this.props.title}
) } } ``` #### 04 函数式组件中的props: 函数式组件中只能通过传值的形式来获取props值。 ```react function Alert(props) { return (
{props.title}
) } ``` ### 三大核心之ref: react中如果要定位到某个DOM元素,可以使用ref进行寻找定位,ref的使用方式分为以下三种: - 直接定义字符串ref:【即将放弃的一种】 - 使用回调的方式获取ref: - createRef函数获取ref:【当前最推荐的一种】 #### 01 字符串定义ref: 字符串定义的ref都会挂载在组件实例的`refs`属性中,访问的时候通过`this.refs.xxx`即可得到。 ```react // 定义 // 获取 this.refs.refInput ``` #### 02 回调函数定义ref: 字符串定义ref会带来无法预知的性能问题,因此会被逐渐废弃掉,可以使用回调函数的形式定义ref。 ```react // 定义 this.refInput = r} className="form-control" type="text" /> // 获取 this.refInput ``` #### 03 createRef函数获取ref: `createRef()`函数可以定义独一无二的ref对象。 ```react // 定义 refInput = React.createRef() // 获取 this.refInput.current ``` ### 事件响应: react中使用类似于`onClick`的方式来定义事件,在事件调取时要注意this的指向问题。 #### 01 this指向问题: react事件定义时经常会出现this指向不正确的问题。 ```react // this指向问题 class App extends React.Component { incrementEvent() { // this指向为undefined console.log(this); } render() { return (
当前数值 {this.state.num}
点击加1
) } } ``` #### 02 bind纠正this指向: 在构造函数中,借助bind能更改this指向的特点,将事件更改为新的函数。 ```react constructor(props) { super(props) this.state = { num: 0 } this.incrementEvent = this.incrementEvent.bind(this); } ``` #### 03 箭头函数解决this指向: 由于箭头函数本身没有this,它依赖于其作用域外层的this,因此可以将事件定义成一个箭头函数。 ```react incrementEvent = () => { this.setState({ num: this.state.num + 1 }) } ``` #### 04 事件对象的获取: react事件中,事件对象直接依赖于原生js事件,在原生js事件中可以获取到事件对象。 ```react changeEvent = (e) => { const { value } = e.currentTarget; this.setState({ content: value }) } ``` ### 生命周期: 组件从创建到死亡要经历一些特定的阶段,在不同的阶段会触发想对应的钩子函数,这些构造函数组成了react组件完整的生命周期。 #### 01 初始阶段: 组件在初始阶段会调用构造器函数和render函数第一次的调用以及组件的相关生命周期函数 - **`constructor`:** 构造器函数,用来初始化组件的状态 - **`componentWillMount`:** 组件即将加载时调用 - **`render`:** 组件渲染虚拟DOM元素 - **`componentDidMount`:** 虚拟DOM元素渲染在页面上,组件加载完毕 #### 02 更新阶段: 组件在更新时,会依次经过更新阀门检测,虚拟DOM生成等过程: - **`render`:** 组件初次加载时也需要render初次渲染。 - **`shouldComponentUpdate(props,curState)`:** 可以看做组件更新的阀门,默认返回值为true,即允许更新,当定义为false时组件不会再更新。 - **`componentWillUpdate(props,curState)`:** 组件在即将更新时调用。 - **`render`:** 组件更新时生成新的虚拟DOM元素,并渲染在页面中。 - **`componentDidUpdate`:** 组件更新完成。 > **`forceUpdate()`**函数会使得更新阀门`shouldComponentUpdate`失效。 #### 03 子组件接收props阶段: 父组件在给组件传递props值会触发**`componentWillReceiveProps`** - **`componentWillReceiveProps(curProps)`:** 组件接受的props值在发生变化时触发该函数。 #### 04 卸载阶段: 组件卸载使用**`ReactDOM.unmountComponentAtNode(rootNode)`**函数,在即将卸载时会触发**`componentWillUnmount`**函数。 - **`componentWillUnmount`:** 组件在即将卸载时触发该函数。 #### 05 常用的生命周期函数: react众多的生命周期函数中,常见的生命周期函数其实只有三个: - **`componentDidMount()`:** 在该生命周期函数中,主要完成数据请求、定时器开启等动作。 - **`componentWillUnmount()`:** 在组件即将卸载时,需要关闭定时器等收尾工作。 #### 06 新旧生命周期函数的异同: 在React16之后,react考虑将一些不常用的生命周期函数逐渐废弃,并新增了两个新的生命周期函数(不常用)。 **废弃:** - **`UNSAFE_componentWillMount`** - **`UNSAFE_componentWillUpdate`** - **`UNSAFE_componentWillReceiveProps`** **新增:** - **`getDerivedStateFromProps`(了解)** - **`getSnapshotBeforeUpdate`(了解)** ### 开发环境搭建: #### 01 webpack版本: 构建webpack5版本的react开发环境。 #### 02 Vite版本: 构建vite4版本的react开发环境。 ### 样式及动画: #### 01 样式绑定: 样式的改变只能通过state值的更改来改变不同的className的值,不同className对应不同的样式。 常用的将类名存放在数组中,操作数组中的数据,进而改变不同的样式。 ```react class App extends Component { state = { classnames: [] } changeShape = (type) => { let list = [...this.state.classnames]; if (list.includes(type)) { list = list.filter(item => item !== type); } else { list.push(type) } this.setState({ classnames: list }) } render() { return (

这里是一段文本

) } } ``` #### 02 动画方案: #### 03 样式冲突: ### Hooks函数: hooks是react 16.8之后加入的新特性和新语法,可以让我们在函数组件中使用state或者其他的react特性,旨在用函数组件替换类组件。 #### 01 useState Hook: React中state相关的hooks函数为**`useState(initValue)`**,返回值则是包含两个元素的数组:**`[xxx,setXxxx]`** - 返回值xxx为为第一次初始化指定的值内部缓存用 - setXxx用作更新状态值xxx的函数。 ```react function Alert() { let [num, setNum] = useState(0); const increment = () => { setNum(num => num += 1) } const decrement = () => { setNum(num => num -= 1) } return (
当前数值为:{num}
) } ``` #### 02 useEffect Hook: useEffect Hook可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)。 可以把useEffect Hook看做三个生命周期函数的组合: ```react // 第二个参数为空 -> 组件render useEffect(() => { console.log("====render====") }) // 第二个参数为空数组 -> 组件componentDidMount useEffect(() => { console.log("====componentDidMount====") timer = setInterval(() => setNum(num => num += 1), 1000); }, []) // 第二个参数为非空数组,监测数组中值的变化,类似于vue中的watch -> componentDidUpdate useEffect(() => { console.log("====time change====") }, [time]) // 第二个参数返回的函数可以认为组件销毁 -> componentWillUnmount useEffect(() => { console.log("====componentDidMount====") timer = setInterval(() => setNum(num => num += 1), 1000); return () => { clearInterval(timer) } }, []) ``` > - 不要在useEffect中改变依赖项的值,这样会造成死循环。 > - 多个不同功能的副作用尽量分开声明,不要写在一个useState中。 #### 03 useRef Hook: Ref Hook可以在函数式组件中存储 / 查找组件内的标签或任意其他数据。 ```react function Box() { let inputRef = useRef(); function getvalue() { console.log(inputRef.current.value) } return ( ) } ``` #### 04 forwardRef: 在父组件中不能使用ref给函数式子组件赋值,必须借助于**`forwardRef(props,ref)`**包裹子组件,并结合**`useImperativeHandle(ref,fn,watch)`**将子组件中的状态值和私有方法暴露。 ```react // 子组件定义 import React, { Fragment, useState, forwardRef, useImperativeHandle } from "react"; const Child = forwardRef((_, ref) => { const [num, setNum] = useState(0); const increment = () => { setNum(num => num += 1) } /** * 将函数式组件中的值和改变值的方法以及私有方法都暴露给父组件 */ useImperativeHandle(ref, () => ({ num, setNum, increment })) return (
当前值为:{num}
) }) export default Child // 父组件调用 const childRef = useRef(); const { increment } = childRef.current; const { setNum } = childRef.current; ``` > **`useImperativeHandle`:** 函数接受三个参数,第一个参数为**`forwardRef`**传递过来的**`ref`**值,第二个参数为一个暴露函数(将需要暴露的数据以对象的形式暴露),第三个参数则是需要监测的参数列表。 > > - 监测参数列表不定义时:暴露函数每次都会重新执行,函数式组件每次更新都会返回新的状态值; > - 监测参数列表为空数组时:暴露函数只会执行一次; > - 监测参数列表不为空时:只要需要监测的状态值更新时,暴露函数就会执行一次。 #### 05 自定义Hook: 对于一些复杂、重复性的状态改变函数,我们可以将其抽离出来组成自定义Hook,在自定义Hook函数中导出组件所需要的值。 ```react import { useState, useEffect } from 'react' const useTime = (time) => { const [count, setCount] = useState(time); const [status, setStatus] = useState(true); useEffect(() => { let timer = null; timer = setTimeout(() => { if (count > 1) { setCount(count => count -= 1); } else { clearTimeout(timer); setStatus(status => status = false); } }, 1000) return () => clearTimeout(timer) }, [count]) return [count, status] } export default useTime ``` #### 06 useLayoutEffect: **`useLayoutEffect`**和**`useEffect`**的使用方式相似,都需要接收一个函数和一个依赖项数组,并且都可以返回一个清理函数,不同之处在于: - **`useEffect`** 是在浏览器重新绘制屏幕之后触发,异步执行,不阻塞浏览器绘制; - **`useLayoutEffect`** 是在浏览器重新绘制屏幕之前触发,同步执行,阻塞浏览器绘制; #### 07 useReducer: 当状态更新逻辑复杂时可以使用**`useReducer`**,`useReducer`可以同时更新多个状态,而且能把对状态的修改从组件中独立出来。 reducer是一个函数,类似于`(prevState, action) => newState`,形参`prevState`表示旧状态,形参`action`表示本次的行为,`newState`表示处理完毕后的新状态。 ```react import { useReducer } from 'react' // 初始state const defaultState = { num: 0 } // state改变函数reducer const reducer = (state, action) => { const newState = { ...state }; const { type, payload } = action; switch (type) { case "DECREMENT": newState.num > 0 ? newState.num -= payload : 0; break; case "INCREMENT": newState.num += payload; break; default: break; } return newState; } // 自定义hook函数返回state值和dispatch函数 const useNumReducer = () => { const [state, dispatch] = useReducer(reducer, defaultState); return [state, dispatch]; } export default useNumReducer ``` #### 08 createContext和useContext: 函数式组件中,如果组件嵌套层级很深,当顶层组件向底层组件传递数据时,传统的方式是通过props一层一层的把数据向下传递。 **`useContext`**结合**`createContext()`**可以轻松实现多层组件间的数据传递。 ```react import React from 'react' ``` #### 09 memo和useMemo: 当父组件被重新渲染的时候,会触发子组件的重新渲染,对于一些只通过props传值的组件,这其实是多出了无意义的性能开销。 借助`React.memo(fn)`将函数组件进行包裹,返回一个新的组件,该组件只会在父组件传递的props值发生变化时才会重新渲染。 ```react import React, { memo } from "react"; const Item1 = memo(props => { console.log("====Item1 Render====") return (
当前值为:{props.num}
) }) export default Item1 ``` **`useMemo()`**函数会返回一个新的数据,该数据只会在所依赖的值发生变化时重新执行,节约开销。 ```react import React, { useState, useMemo } from "react"; const [num, setNum] = useState(0); const [time, setTime] = useState(new Date().getTime()); // 类似于vue中的计算属性 const isodd = useMemo(() => { console.log("依赖于num") return num % 2 == 0; }, [num]) ```