陆续记录React开发的基础知识以及开发模式。
开发环境
create-react-app
通过create-react-app工具快速创建React项目。
bash1 2 3 4 5 6 7 8 9
| npm install -g create-react-app create-react-app my-app
npx create-react-app my-react-app
cd my-react-app npm start
|
React框架
bash1
| npx create-next-app@latest
|
渲染
React DOM和原生DOM是一一对应的。
列表渲染
可以使用 map
, filter
等方法来创建列表。
实例:创建列表并为每一个列表项绑定 key
。
jsx1 2 3 4 5 6 7 8 9
| const list = [1, 2, 3, 4, 5]
function List() { const listItems = list.map((number, index) => <li key={number.toString()}>{number}</li>)
return ( <ul>{ listItems }</ul> ) }
|
一般选择列表项中唯一的字段作为列表项的key。
如果列表项中没有唯一的字段,则可以选择 index
作为key。
JSX
JSX基本语法
JSX是React的模板语言。
JSX是JS的扩展。
JSX最外层只能有一个根节点,可以使用 <Fragment></Fragment>
包裹,也可以简写为 <>...</>
。
JSX中使用自定义属性需要再DOM中添加前缀 data-
。
JSX中使用js变量或者表达式需要将其放在 {}
中。
JSX中不能使用 if...else
语法,但是可以使用三元运算符代替。
JSX中的 class
需要写成 className
。
JSX中的 for
要写成 htmlFor
。
JSX样式
React中可以使用camelCase的格式来设置内联样式。
jsx1 2 3 4 5 6 7 8 9 10 11 12
| function Test() { const paragraphStyle = { color: 'red', fontSize: 14, } return ( <> <p style={paragraphStyle}>测试</p> </> ) }
|
JSX注释
jsx1 2 3 4 5 6 7 8 9 10 11 12 13
| function Test() { const paragraphStyle = { color: 'red', fontSize: 14, } return ( <> {/* 这是一个JSX注释 */} <p style={paragraphStyle}>测试</p> </> ) }
|
JSX处理数组
JSX中可以插入数组,数组会被自动展开。
jsx1 2 3 4 5 6 7 8 9
| function List({list}) { const listHtml = list.map(item => <li>{ item }</li>) return ( <ul> { listHtml } </ul> ) }
|
组件
React的组件都需要首字母大写。
组件不要嵌套定义,永远将组件定义在顶层。
函数组件
jsx1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import React, { useState, useEffect } from 'react'
// 接受props function Hello(props) { useEffect(() => { // 组件已挂载
return () => { // 组件卸载前和更新前 }
}, []) // 空数组表示只在组件挂载时执行一次
return ( <h1>Hello, { props.name }</h1> ) }
// 解构props function Hello({name}) { return ( <h1>Hello, { name }</h1> ) }
|
useEffect
允许组件在每次渲染后执行副作用,第二个参数设置为[]
,可以确保组件只在挂载后执行一次。
类组件
jsx1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import React from 'react'
class Hello extends React.Component { // 构造方法不是必须的 constructor(props) { super(props) this.state = { name: props.name, } }
componentDidMount() { // 组件已挂载 } // render方法是必须的 render() { return ( <h1>Hello, { this.state.name }</h1> ) } }
|
函数组件和类组件的区别
区别:函数组件开销小,类组件开销大(因为需要创建实例),对于有状态的组件可以采用函数组件+hooks的方式开发,
结论:只使用函数组件+hooks的方式,尽量不用类组件。
受控组件和非受控组件
如果一个组件由props驱动则为受控组件。
如果一个组件由state驱动 则为非受控组件。
深层props传递
Context
可以用于向后代组件传递变量。
组件通信
父子组件通信
使用props
兄弟组件通信
发布订阅,redux
组件和后代组件通信
发布订阅,redux,Context插件
状态管理
state
React把组件看成是一个状态机,用户通过与组件的交互来实现不同的状态,然后渲染不同的UI。
React中,只需要更新组件的 state
,就可以重新渲染组件。
state用于保存渲染中的数据,类似一个快照,setState
的时候会触发页面的重新渲染。
注意:state的初始化是在构造函数中,不要直接更新state,更新state需要调用setState。
state实例:
jsx1 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
| class Person extends React.Component { constructor(props) { super(props) this.state.name = props.name this.state.age = props.age }
// 生命周期钩子函数:组件已挂载 componentDidMount() { // }
// 生命周期钩子函数:组件即将卸载 componentWillUnmount() { // }
// 类方法 updatePerson() { this.setState({ name: '', }) }
render() { return ( <> <p>姓名:{ this.state.name}</p> <p>年龄:{ this.state.age}</p> </> ) } }
|
状态只在该组件内可访问,其他组件不可访问。
React组件的数据自顶向下单向流动。
由于状态的更新是异步的,所以setState的时候需要接受一个函数来设置state。
jsx1 2 3
| this.setState((prevState, props) => ({ counter: prevState.counter + props.increment }))
|
flushSync
如果想要强制react刷新DOM以便在DOM更新后进行一些操作,可以使用 flushSync
。
jsx1 2 3 4 5 6 7 8 9 10
| import { useState, useRef } from 'react' import { flushSync } from 'react-dom'
flushSync(() => { setTodos([ ...todos, newTodo]); })
// 上面的todos在这里已经更新了 listRef.current.lastChild.scrollIntoView()
|
组件传参
props
React父子组件之间可以通过 props
来传参(包括js的所有数据类型以及JSX,可以通过传递JSX来实现类似Vue的插槽的功能)。
props和state的区别是:props不可变,state可以变更。
props通过父组件更新props来更新。
state通过set来更新。
实例:
jsx1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| // 方式1: function Hello(props) { return <h1>hello, { props.name }</h1> }
// 方式2: function Hello({ name }) { return <h1>hello, { name }</h1> }
// 调用组件 function Home() { const myName = 'Mason' return <Hello name={myName} /> }
|
props默认值
如果组件的props需要默认值怎么设置呢?
- 函数组件可以通过直接设置props中变量的默认值进行设置。
- 函数组件和类组件都可以通过设置
defaultProps
来设置默认值。
- 如果父组件没有传递子组件需要的变量或者传了
undefined
,则子组件会使用默认值。
一般使用第一种。
jsx1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| // 方法一 function Person({ name = '', age = 0, gender = 'male' }) { // }
// 方法二 function Person({ name, age, gender }) { // }
Person.defaultProps = { name: '', age: 0, gender: 'male', }
|
props校验
props的校验使用 prop-types
库。
安装prop-types
使用prop-types
jsx1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| // 引入 import PropTypes from 'prop-types'
// 组件 function Person( name, age ) { return ( <> <p>{ name }</p> <p>{ age }</p> </> ) }
// 指定了props变量的类型和是否必须 Person.propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number.isRequired, }
|
内容分发
- 在父组件中调用子组件并将JSX传入内层,子组件中调用props.children获取JSX。
jsx1 2 3 4 5 6 7 8 9
| // ParentComponent.js function ParentComponent(props) { return <div>{props.children}</div> }
// Usage <ParentComponent> <h1>Hello, World!</h1> </ParentComponent>
|
- 父组件将一个函数(这个函数返回JSX)作为props传给子组件,子组件调用该函数获取到JSX。
jsx1 2 3 4 5 6 7
| // ParentComponent.js function ParentComponent(props) { return <div>{props.render()}</div>; }
// Usage <ParentComponent render={() => <h1>Hello, World!</h1>} />
|
事件
React中的事件命名都采用的是camelCase的形式。
常用事件
React中的常见事件:
- onClick:元素被点击时触发该事件。
- onChange:输入框内容变化时触发该事件。
- onSubmit:提交表单时触发该事件。
- onMouseOver/onMouseOut:鼠标移入或移出时触发该事件。
- onKeyDown/onKeyUp:键盘按下或弹起触发该事件。
- onFocus/onBlur:获取焦点或失去焦点触发该事件。
- onScroll:滚动时触发该事件。
- onLoad/onError:图片加载成功或失败时触发该事件。
除了以上的常用事件之外还有很多其他事件。
事件分类
- 鼠标事件
- onContextMenu
- onClick
- onDoubleClick
- onMouseDown
- onMouseUp
- onMouseEnter
- onMouseLeave
- onMouseMove
- onMouseOut
- onMouseOver
- 键盘事件
- onKeyPress
- onKeyDown
- onKeyUp
- 触摸事件
- onTouchStart
- onTouchEnd
- onTouchMove
- onTouchCancel
- 拖拽事件
- onDrop
- onDrag
- onDragStart
- onDragEnd
- onDragEnter
- onDragLeave
- onDragOver
- onDragExit
- 剪贴板事件
- 表单事件
- onChange
- onInput
- onSubmit
- 焦点事件
- UI事件
- 窗口事件
- 组成事件
- onCompositionStart
- onCompositionEnd
- onCompositionUpdate
- 图片事件
- 多媒体事件
- onAbort
- onCanPlay
- onCanPlayThrough
- onDurationChange
- onEmptied
- onEncrypted
- onEnded
- onError
- onLoadedData
- onLoadedMetadata
- onLoadStart
- onPause
- onPlay
- onPlaying
- onProgress
- onRateChange
- onSeeked
- onSeeking
- onStalled
- onSuspend
- onTimeUpdate
- onVolumeChange
- onWaiting
实例
绑定的事件后需要传入一个事件处理函数。
onClick事件实例:
jsx1 2 3 4 5 6 7 8 9
| function submitHandler(e) { // 处理事件 }
function Button() { return ( <button onClick={(e) => {submitHandler(e)}}>提交</button> ) }
|
在类组件中需要注意 this
的指向问题,区别普通函数和箭头函数。
父组件通过props将事件处理函数传递给子组件处理
可以直接将事件处理函数卸载组件中,也可以将事件处理函数作为 props
传递给子组件,在子组件中处理。
jsx1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| // 子组件 function Button({onClickHandler, buttonText}) { return ( <button onClick={(e) => {onClickHandler(e)}}>{ buttonText }</button> ) }
// 父组件,调用子组件 function Form() { function handler() { alert(123) } return ( <Button onClickHandler={handler}></Button> ) }
|
事件传播
如果父元素和子元素绑定了同一个事件,触发子元素的事件后会一层一层传播到外层元素并触发事件。
阻止事件冒泡
方法:stopPropagation
jsx1 2 3 4 5 6 7 8 9
| function Button() { function submit(e) { e.stopPropagation() }
return ( <button onClick={(e) => {submit(e)}}>提交</button> ) }
|
阻止事件默认行为
方法:preventDefault
jsx1 2 3 4 5 6 7 8 9 10
| function Link() { const url = 'https://www.google.com/' function clickLinkHandler(e) { e.preventDefault() }
return ( <a src={url} onClick={(e) => {clickLinkHandler(e)}}>链接</a> ) }
|
捕获子元素事件
可以在父元素上绑定 onClickCapture
事件来监听子元素的事件。
jsx1 2 3 4 5 6 7 8 9 10 11
| function Button() { function onClickCaptureHandler(e) { console.log(e) }
return ( <div onClickCapture={(e) => {onClickCaptureHandler(e)}}> <button onClick={e => e.stopPropagation()}>提交</button> </div> ) }
|
条件渲染
使用if…else判断来返回不同的React DOM
jsx1 2 3 4 5 6 7
| function Home(props) { if (props.isLogin) { return <UserHome /> } else { return <GuestHome /> } }
|
jsx1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import React, { useState } from 'react'
function Home(props) { const [isLogin, setIsLogin] = useState(false)
function toggleLogin(e) { setIsLogin(!isLogin) }
return ( <div> <p>{ toggleLogin ? '已登录' : '未登录' }</p> <button onClick={(e) => {toggleLogin(e)}}>切换</button> </div> ) }
|
使用&&运算符
jsx1 2 3 4 5 6 7 8 9
| function MessageBox(props) { const msgList = props.messages
return ( <div> { msgList.length && <p>有{ msgList.length }条信息未读</p> } </div> ) }
|
JSX对于 &&
运算符的处理:
如果左侧为 truthy
,那么之间返回右侧的值。
如果左侧为 falsy
,那么直接返回false且不会被渲染。
三元运算符
jsx1 2 3 4 5 6 7
| function Home(props) { return ( <div> { props.isLogin ? <UserHome /> : <GuestHome /> } </div> ) }
|
阻止组件被渲染
让render函数返回null即可阻止组件被渲染。
组件API
注意:以下的API中的callback都会在重新渲染后被调用。
setState
用法:setState(object nextState[, function callback])
setState会合并旧的state和新的state(以新的state为准)。
setState会重新渲染组件。
修改state只能通过setState。
replaceState
用法:replaceState(object nextState[, function callback])
replaceState会替换state,只会保留新的state。
setProps
用法:setProps(object nextProps[, function callback])
setProps会合并旧的props和新的props(以新的props为准)。
setProps会重新渲染组件。
replaceProps
用法:replaceProps(object nextProps[, function callback])
replaceProps会替换props,只会保留新的props。
forceUpdate
用法:forceUpdate([function callback])
forceUpdate会使组件重新渲染。
避免使用forceUpdate。
findDOMNode
用法:DOMElement findDOMNode()
返回值:DOM元素DOMElement
避免使用findDOMNode
已废弃。
isMounted
用法:bool isMounted()
isMounted用于判断组件是否挂载。
已废弃。,现在采用组件生命周期钩子函数判断是否已经挂载。
jsx1 2 3 4 5 6 7
| componentDidMount() { this.mounted = true }
componentWillUnmount() { this.mounted = false }
|
组件生命周期
参考另一篇我的笔记。
Ajax
组件内一般在 componentDidMount
中请求数据,获取到数据后存储在state中,再用setState重新渲染组件。
在组件卸载之前 componentWillUnmount
内取消未完成的请求。
实例:
jsx1 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 51 52 53 54 55 56 57
| import React, { Component } from 'react' import axios from 'axios'
class MyComponent extends Component { constructor(props) { super(props) this.state = { data: null, error: null } this.source = axios.CancelToken.source() }
componentDidMount() { this.fetchData() }
componentWillUnmount() { // 取消未完成的请求 this.source.cancel('Component unmounted') }
async fetchData() { try { const response = await axios.get('https://api.example.com/data', { cancelToken: this.source.token }) this.setState({ data: response.data }) } catch (error) { if (axios.isCancel(error)) { console.log('Request canceled', error.message) } else { this.setState({ error: error.message }) } } }
render() { const { data, error } = this.state
if (error) { return <div>Error: {error}</div> }
return ( <div> {data ? ( <div>Data: {JSON.stringify(data)}</div> ) : ( <div>Loading...</div> )} </div> ) } }
export default MyComponent
|
表单与事件
比如react处理input:value绑定state,输入时更新state。
组件内处理输入:
jsx1 2 3 4 5 6 7 8 9 10 11 12 13
| import { useState } from 'react'
function Form() { const [inputValue, setInputValue] = useState('')
function inputHandler(e) { setInputValue(e.target.value) }
return ( <input type="text" value={inputValue} onChange={inputHandler} /> ) }
|
父组件处理输入:父组件通过props传递绑定的变量给子组件,父组件通过props传递处理函数给子组件。
jsx1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { useState } from 'react'
function Input({ inputValue, inputHandler }) { return ( <input type="text" value={inputValue} onChange={inputHandler} /> ) }
function Form() { const [v, setV] = useState('')
return ( <Input inputValue={v} inputHandler={setV} /> ) }
|
select
jsx1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import { useState } from 'react'
function Form() { const [selectValue, setSelectValue] = useState('') function submitHandler(e) { e.preventDefault() }
function changeSelectHandler(e) { setSelectValue(e.target.value) } return ( <form onSubmit={submitHandler}> <select value={selectValue} onChange={changeSelectHandler}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> </form> ) }
|
多个表单处理
如果有多个input元素,可以给每一个元素添加一个name属性,并根据name进行处理。
jsx1 2 3 4 5 6 7 8 9 10 11 12 13
| function Form() { function handleInputChange(e) { const name = event.target.name
// 根据name处理 }
return ( <input name="name1" type="text" onChange={handleInputChange} /> <br /> <input name="name2" type="text" onChange={handleInputChange} /> ) }
|
Refs
React提供了Refs
(类似Vue的Refs),可以绑定到任何组件上,可以通过引用Refs拿到组件实例。
useRef
jsx1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import React, { useRef, useEffect } from 'react'
const MyComponent = () => { const inputRef = useRef(null)
useEffect(() => { // 组件挂载后或者更新前,将焦点设置到输入框 inputRef.current.focus() }, [])
const handleClick = (e) => { // 在点击按钮时,获取输入框的值 alert(inputRef.current.value) }
return ( <div> <input type="text" ref={inputRef} /> <button onClick={e => {handleClick(e)}}>Get Value</button> </div> ) }
export default MyComponent
|
路由
基本用法
导航区链接:
jsx1 2 3
| import Link from 'react-router-dom'
<Link to="/Home">首页</Link>
|
展示区Route:
jsx1 2 3 4 5
| // 引入需要展示的组件 import Home from './components/Home'
// 根据路由展示组件 <Route path="/Home" component={Home} />
|
注意:
普通组件存放在components下,路由组件存放在pages下。
Switch
Switch组件用于在多个路由规则之间进行选择,并且在找到第一个匹配的路由后停止匹配。
定义路由:在Switch组件内部定义路由规则。确保将最通用的路由放在最后,以避免它被其他更具体的路由覆盖。
jsx1 2 3 4 5 6 7
| <Router> <Switch> <Route path="/about" component={About} /> <Route path="/contact" component={Contact} /> <Route path="/" component={Home} /> </Switch> </Router>
|
在上面的示例中,如果URL匹配/about
,则将渲染About
组件。如果URL匹配/contact
,则将渲染Contact
组件。如果URL未匹配任何其他路由,则将渲染Home
组件。
可选步骤:还可以在Switch组件的末尾添加一个没有path属性的Route组件,作为404页面。
jsx1 2 3 4 5 6 7 8
| <Router> <Switch> <Route path="/about" component={About} /> <Route path="/contact" component={Contact} /> <Route path="/" component={Home} /> <Route component={NotFound} /> </Switch> </Router>
|
Switch组件确保只有一个匹配的路由会被渲染。这对于避免多个路由同时匹配的情况非常有用,从而保证了预期的路由行为。
Redict
可以使用Redict兜底,当所有路由都不匹配时跳转到Redict指定的路由。
jsx1 2 3 4 5 6 7 8
| <Router> <Switch> <Route path="/about" component={About} /> <Route path="/contact" component={Contact} /> <Route path="/" component={Home} /> <Redict to="/home" /> </Switch> </Router>
|
路由中携带参数
jsx1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| // 路由链接中携带参数 <Link to="/detail/2832737" />
// 注册路由,定义路由中接受的参数 <Route path="/detail/:id" component={Detail} />
// 在路由对应组件内获取参数 function Detail(props) { const { id } = props.match.params
return ( <div> { id } </div> ) }
export default Detail
|
路由中携带查询参数
jsx1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| // 路由链接中携带查询参数 <Link to="/detail?id=2832737" />
// 注册路由 <Route path="/detail" component={Detail} />
// 在路由对应组件内获取查询参数 function Detail(props) { const { search } = props.location const searchQuery = qs.parse(search.slice(1))
return ( <div> { searchQuery } </div> ) }
export default Detail
|
在组件内跳转路由
调用push, replace, goBack, goForward, go方法。
完整实例
- 安装
bash1
| npm install -S react-router-dom
|
- 创建Home组件
jsx1 2 3 4 5 6 7 8 9 10 11 12
| // Home.js import React from 'react'
const Home = () => { return ( <div> <h2>Welcome to Home</h2> </div> ) }
export default Home
|
- 定义路由
jsx1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| // App.js import React from 'react' import { BrowserRouter as Router, Route, Switch } from 'react-router-dom' import Home from './Home'
const App = () => { return ( <Router> <div> <Switch> {/* Define your routes here */} <Route path="/home" component={Home} /> {/* Add more routes if needed */} </Switch> </div> </Router> ) }
export default App
|
现在,当访问/home
时将渲染 Home
组件。
Hooks
useState
用于组件状态管理。
如果需要依赖于老的状态来更新新的状态,则可以传入函数来作为setState的参数,其中函数的参数为老的值,返回值为新的值。
jsx1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { useState } from 'react'
function Demo(props) { const [count, setCount] = useState(0)
function incressCount() { // 直接传入值 setCount(count + 1)
// 传入函数,函数的参数是原值,返回新的值 setCount(count => count + 1) }
return ( <button onClick={incressCount}>加一</button> ) }
|
如果要用setState更新对象,则需要将新的state设置为旧的state的展开再加上更新的字段。可以使用immer
库来简化代码。
useEffect
用于模拟组件生命周期中的钩子函数,用于在组件渲染后执行副作用,接受一个回调函数(副作用)作为参数并在组件挂载、更新、卸载时执行,第二个参数是状态数组(state数组)。当状态数组发生变化时,react会重新渲染并执行副作用(回调函数)。
如果传入非空数组,则当数组中的这些state变化时会执行副作用。
如果传递一个空数组 []
,则表示不检测任何state,只在组件挂载时执行一次副作用。
如果不传递第二个参数,则会监测所有的state,初始化的时候以及每个状态更新的时候都会执行副作用。
使用useEffect实现组件的三个钩子函数:
jsx1 2 3 4 5 6
| useEffect(() => { // componentDidMount 相关的操作 return () => { // componentWillUnmount 在组件卸载前或者重新渲染前执行相关的清理操作 } }, [])
|
jsx1 2 3
| useEffect(() => { // componentDidUpdate 相关的操作 })
|
jsx1 2 3 4 5
| useEffect(() => { return () => { // componentWillUnmount 在组件卸载前或者重新渲染前执行相关的清理操作 } }, [])
|
useContext
使用useContext可以方便的在组件中访问全局的上下文数据。
- 创建上下文对象:
jsx1 2 3 4 5
| // 引入createContext import React, { createContext } from 'react'
// 创建上下文对象 const MyContext = createContext()
|
- 向子组件提供上下文对象
jsx1 2 3 4 5 6 7 8 9 10
| import ChildrenComponent from './ChildrenComponent'
// 向子组件提供上下文对象 function Demo(props) { const sharedData = 'shared data' <MyContext.Provider value={sharedData}> <ChildrenComponent></ChildrenComponent> <MyContext.Provide/> }
|
- 子组件中访问上下文对象
jsx1 2 3 4 5 6 7 8 9 10 11 12
| import React, { useContext } from 'react'
function ChildrenComponent(props) { // 使用useContext获取上下文对象 const data = useContext(MyContext)
return ( <div> { data } </div> ) }
|
- 当上下文数据发生变化时,使用了该数据的组件会自动重新渲染。
- 如果上下文发生了嵌套,则使用了上下文数据的组件会获取最接近的Provider提供的值。
- 如果上下文数据变化较大或者变化频繁,可以使用memo优化性能。
useReducer
可以代替useState,用于在组件中使用复杂的状态逻辑,接受一个reducer函数和初始状态作为参数,并返新的状态和dispatch函数。
- 定义reducer函数
reducer函数接受当前状态和一个操作(action),并根据操作的类型来更新状态。返回更新后的状态和dispatch函数。
js1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function reducer(state, action) { switch (action.type) { case 'INCREMENT': return { count: state.count + 1 } case 'DECREMENT': return { count: state.count - 1 } default: return state } }
|
- 定义初始状态
js 1 2 3
| const initialState = { count: 0, }
|
- 在组件中应用reducer
jsx1 2 3 4 5 6 7 8 9 10 11 12 13
| import React, { useReducer } from 'react'
function Counter() { const [state, dispatch] = useReducer(reducer, initialState)
return ( <div> {state.count} <button onClick={() => dispatch({ type: 'INCREMENT' })}>increment</button> <button onClick={() => dispatch({ type: 'DECREMENT' })}>decrement</button> </div> ) }
|
可以使用useImmerReducer
简化代码。
useCallback
可以用来优化函数的性能,接受一个回调函数和依赖项数组作为参数,并返回一个经过优化的回调函数。该函数只有在以来数组发生变化时才会重新创建。如果以来数字不变,useCallback会返回之前缓存的回调函数引用,避免了重新创建函数。
使用场景:
传递函数给子组件
当将回调函数作为属性传递给子组件时,可以使用useCallback来缓存回调函数以避免不必要的子组件的重新渲染。
避免额外的开销
如果回调函数依赖于组件的状态或者属性,但是不需要每次组件渲染时都创建新的函数实例,这个时候可以使用useCallback将相关依赖项添加到依赖数组中,可以避免不必要的重新创建函数实例的开销。
当useEffect或者useMemo的依赖项是回调函数
当将回调函数作为useEffect或者useMemo的依赖项时,可以使用useCallback来缓存回调函数,可以确保依赖项变化时回调函数能更新。
避免重复创建回调函数
如果需要频繁创建回调函数,使用useCallback可以减少不必要的创建开销。
useCallback实例:
jsx1 2 3 4 5 6 7 8 9 10 11 12
| import React, { useCallback } from 'react'
function Demo({ handler }) { // 使用useCallback缓存回调函数 const cb = useCallback(() => { // 自己的逻辑
// 执行handler handler()
}, [handler]) }
|
这里,Demo组件接收了一个handler函数,使用useCallback缓存了handler函数。当handler发生变化时cb才会被重新创建。
如果不使用useCallback,那么每次Demo组件渲染时cb都会被重新创建。
useMemo
用于组件渲染过程中进行性能优化,避免不必要的计算和重复渲染。
useMemo
接受两个参数:一个计算函数和依赖数组,组件渲染时会执行计算函数并将计算结果缓存起来,当依赖数组发生变化时会重新执行计算函数。这样就可以避免每次渲染时重复计算。
使用场景:
缓存计算结果
避免不必要的重新渲染
如果一个组件依赖一个状态或者属性,但是这个状态或者属性的变化不会影响组件的渲染时,可以使用useMemo缓存结果避免重新渲染。
优化子组件渲染
当将一个计算结果作为属性传给子组件时,可以使用useMemo缓存计算结果避免每次父组件渲染时都重新计算并传给子组件触发子组件的重新渲染。
比较操作
当需要进行比较操作时,比如数组或者对象,可以使用useMemo缓存比较结果,避免每次渲染时重新比较。
useMemo实例:
jsx1 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
| import React, { useMemo, useState } from 'react'
function ExpensiveComponent() { // 计算函数 function calc() { return Math.random() }
// 使用useMemo缓存计算结果 const expensiveValue = useMemo(() => calc(), [])
return ( <div> { expensiveValue } </div> ) }
function App() { const [count, setCount] = useState(0)
return ( <div> <button onClick={() => setCount(count + 1)}>increment</button> <ExpensiveComponent /> </div> ) }
|
这里为了避免每次渲染时都重新计算expensiveValue,使用了useMemo来缓存计算结果。
useMemo第二个参数传入空数组,确保了只在组件第一次渲染时执行一次计算函数,并将计算结果缓存起来。当父组件中的count变化时,ExpensiveComponent组件重新渲染但是不会重新计算expensiveValue。
useRef
当我们希望组件记住某些数据,但不想让这些数据触发新的渲染时,可以使用ref,设置ref的值不会引发重新渲染。
可以使用ref来存储计数器id、DOM以及其他不影响组件渲染的数据。
比如ref可以存储组件实例。
jsx1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { useRef } from 'react'
function Demo() { const myRef = useRef()
function getRef() { console.log('myRef', myRef) }
return ( <button ref={myRef} onClick={getRef}>按钮</button> ) }
export default Demo
|
对于自定义组件,如果需要在自定义组件中绑定ref,需要将自定义组件暴露出来(组件函数使用forwardRef包裹)。
jsx1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { forwardRef, useRef } from 'react'
const MyInput = forwardRef((props, ref) => { return <input {...props} ref={ref} /> })
export default function Form() { const inputRef = useRef(null)
function handleClick() { inputRef.current.focus() }
return ( <> <MyInput ref={inputRef} /> <button onClick={handleClick}> 聚焦输入框 </button> </> ) }
|
以上的方式会暴露整个MyInput组件。
如果只想要暴露部分api可以使用useImperativeHandle
实现。
jsx1 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
| import { useRef, forwardRef, useImperativeHandle, } from 'react'
const MyInput = forwardRef((props, ref) => { const realInputRef = useRef(null) useImperativeHandle(ref, () => ({ // 只暴露focus,没有别的 focus() { realInputRef.current.focus(); }, })) return <input {...props} ref={realInputRef} /> })
export default function Form() { const inputRef = useRef(null)
function handleClick() { inputRef.current.focus() }
return ( <> <MyInput ref={inputRef} /> <button onClick={handleClick}> 聚焦输入框 </button> </> ) }
|
useLayoutEffect
和useEffect类似,但是它在DOM更新后,在浏览器重排重绘之前触发,可以在DOM更新后立刻执行副作用。
一般用于需要在DOM更新之后获取到最新的布局和DOM的场景。
实例:
jsx1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import React, { useLayoutEffect, useRef } from 'react'
function Demo() { const myRef = useRef() useLayoutEffect(() => { // DOM已更新,进行一些获取、操作最新DOM的操作 const {width, height} = myRef.current.getBoundingClientRect() }, [])
return ( <div ref={myRef}>demo</div> ) }
|
需要注意的是,useLayoutEffect会阻塞浏览器渲染,因此不要在useLayoutEffect中做非常耗时的操作,仅用于获取新布局或者DOM。
Redux
Redux是react状态管理的库,可以集中管理多个组件共享的状态。
安装
bash1
| npm install -S redux react-redux
|
使用
- 创建redux store
redux store包含了整个state,在使用之前需要先创建一个store,通常在根组件中创建并配置redux store。
- 定义actions
actions是描述发生了什么的普通的js对象,需要定义action creators来创建actions。
- 定义reducers
reducers指定了应用状态如何响应actions并更新状态的逻辑,每个reducer都负责管理全局state树中的一部分数据。
- 使用react-redux链接react组件和redux store
react-redux库提供了一些API来连接react组件和redux store。可以通过<Provider>
组件提供store并使用connect
函数来连接react组件。
实例
App.js:
jsx1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| // App.js import React from 'react' import { Provider } from 'react-redux' import { createStore } from 'redux' import rootReducer from './reducers' import MyComponent from './MyComponent'
// 创建redux store const store = createStore(rootReducer)
const App = () => { return ( // 通过Provider组件向后代组件提供state <Provider store={store}> <MyComponent /> </Provider> ) }
export default App
|
MyComponent.js:
jsx1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| // MyComponent.js import React from 'react' import { connect } from 'react-redux' import { incrementCounter } from './actions'
const MyComponent = ({ counter, increment }) => { return ( <div> <p>Counter: {counter}</p> <button onClick={e => {increment(e)}}>Increment</button> </div> ) }
const mapStateToProps = state => ({ counter: state.counter })
const mapDispatchToProps = dispatch => ({ increment: () => dispatch(incrementCounter()) })
// 使connect连接到redux store export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
|
actions.js:
jsx1 2 3 4 5 6
| // actions.js export const INCREMENT_COUNTER = 'INCREMENT_COUNTER'
export const incrementCounter = () => ({ type: INCREMENT_COUNTER })
|
reducers.js:
jsx1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| // reducers.js import { combineReducers } from 'redux' import { INCREMENT_COUNTER } from './actions'
const counterReducer = (state = 0, action) => { switch (action.type) { case INCREMENT_COUNTER: return state + 1 default: return state } }
const rootReducer = combineReducers({ counter: counterReducer })
export default rootReducer
|
性能优化
组件性能优化
使用PureComponent
优化组件渲染,PureComponent重写了shouldComponentUpdate,只有state或者props发生变化时才会返回true(浅比较)。
参考
- https://www.runoob.com/react/react-tutorial.html
- https://zh-hans.react.dev/learn
- https://blog.csdn.net/NAMECZ/article/details/122854282
- https://blog.csdn.net/qq_42696432/article/details/133943462
- https://blog.csdn.net/study_way/article/details/131752538
- https://www.jb51.net/article/272222.htm