React基础语法整理

安装:
yarn create react-app reatc-lesson --template typescript
yarn create 创建一个react-app的应用 项目名称 typescript 的模板

react-app 官方地址

https://create-react-app.bootcss.com/docs/adding-typescript

react 语法文档

https://zh-hans.react.dev/learn#writing-markup-with-jsx

语法

基础语法

组件函数

1、必须使用大驼峰命名;

2、return 之前可以定义组件使用的数据;

3、使用一对大括号即可使用定义对象的属性;

4、reactNode 不支持直接渲染布尔值,布尔值用来条件渲染处理或者将其转成字符串来使用。

5、reactNode 不支持直接渲染对象,需要将其转为字符串才能直接渲染。

5、ReactNode 类型用来表示可以在React 组件中渲染的任何内容的一种类型,可以直接渲染:字符串、数字、元素或者或者包含这些类型的数组。

//return () 用来存放html组件的
function MyBanner(){//return 之前可以定义组件使用的数据const user = {name: 'Joe',}{/*  */} //注释语法{/* 返回组件的根元素*/}return(<h1 className>Hello MyBanner {user.name}</h1>)(/* 也可以不使用括号返回 */)return <h1 className>Hello MyBanner {user.name}</h1>
}

注释

语法: {/* */}

{/*  */} 

添加class名称

只能使用小驼峰命名的属性名:className 来指定一个css 的class,使用方式跟class一样

<img className="avatar" />
.avatar {border-radius: 50%;
}

根据数据渲染视图

将数据放在元素标签中,放到一对大括号里,例如:{user.name}

return (<h1>{user.name}</h1>
)

还可以将定义的数据放到元素属性上,但是必须使用大括号 而非引号。例如, className={avatar} 将avatar 字符串传递给 className,作为css的class,而非通过变量传递给className。但 src={user.imageUrl} 会读取js 的 user.imageUrl 这个变量,然后将其读取的值作为 src 属性传递

return (
<img className='avatar' src={user.imageUrl} />
)

一个组件中返回多个元素

1、在React语法中,要去一个组件的返回值只能有一个根元素。

2、使用div 包裹多个元素是一种常见的方法,但有时会导致不必要的DOM层次结构。

解决:引入 <></> 作为一种更简洁的方法。

<></> 是React 中的一种称为 Fragment 的语法。它是一种用于在组件中返回多个元素而不需要创建额外DOM元素的简洁方式

return (<><h1>{user.name}</h1><imgclassName="avatar"src={user.imageUrl}alt={'Photo of ' + user.name}style={{color:'red'width: user.imageSize,height: user.imageSize}}/></>);

添加style样式

语法:style={{}} ,是style={} 大括号内的一个普通 {} 对象。当样式依赖 js 变量时,可以使用 style属性

return (<><h1>{user.name}</h1><imgclassName="avatar"src={user.imageUrl}alt={'Photo of ' + user.name}style={{color:'red'width: user.imageSize,height: user.imageSize}}/></>);

使用style样式的方式

function MyComponent(){const styles = {color:'red',fontSize:'16px',fontWeight:'bold'}return (<div style={styles}>这是一个文本</div>;)
}{/* 使用css模块化文件 */}
import styles from './styles.module.css';function MyComponent() {return (<div className={styles.myClass}>这是一个文本</div>)
}

条件渲染

React没有特殊语句来编写条件语句,使用的就是普通的 js 代码。例如:if

  const user = {name: 'Joe',age: 32,isAdmin: false,isBanned: true,}let content;if(user.isAdmin){content = <h2>Welcome, {user.name}!</h2>}else {content = <h2>You are not an admin.</h2>}

或者通过组件:三目元算符

<div>{user.isBanned ? (<MyBanner />) : (<MyButton />)}</div>

又或者是 if 引入组件

let content;
if (isLoggedIn) {content = <MyBanner />
} else {content = <MyBanner2 />;
}
return (<div>{content}</div>
);

又或者是 &&

<div>{isLoggedIn && <AdminPanel />}
</div>

渲染列表

也是依赖js特性,例如for循环 和 map函数来渲染组件。

写法1:组件外循环

const products = [{ title: 'Cabbage', id: 1 },{ title: 'Garlic', id: 2 },{ title: 'Apple', id: 3 },
]{/* 注意 li 里有一个key属性。对应列表每一个元素,都应该传递一个字符串或数字给key,用于在其他 兄弟节点中唯一标识该元素,key是什么数据跟vue循环的key是一样的 */}const listItem = user.products.map(item => <li key={item.id}>{item.title}</li>)<ul>{listItems}</ul>

写法2,在组件内循环

<ul>{user.products.map((item,index) => (<li key={index}>{item.title}</li>))}
</ul>

写法3:带样式

const itemList = user.products.map(item => <li key={item.id}style={{color: item.id % 2 === 0 ? 'red' : 'blue'}}>{item.title}</li>
)<ul>{itemList}
</ul>

for循环,没其他写法

const itemList2 = []for(let i = 0; i < user.products.length; i++){itemList2.push(<li key={i}>{user.products[i].title}</li>)}<ul>{itemList2}</ul>

响应事件

基本使用

也就是点击事件咯,语法是 onClick={函数}

function App() {function handleClick (aaa :any){
{/* 在默认情况,事件监听器的参数aaa 是一个事件对象(通常命名event,我现在命名aaa),这个事件包含事件类型、目标元素等 */}console.log('clicked',aaa) console.log(aaa.target) {/* 获取目标元素*/}}return (<div className="App">{/* 基础写法1 */}<button onClick={handleClick}>点击响应事件</button>{/* 内联事件函数处理 */}<button onClick={function handleClick() {alert('hello')}}>OK1</button>{/* 简洁箭头函数 */}<button onClick={() => {alert('你点击了我!');}}></div>);
}
事件监听传参

使用箭头函数,在事件监听中使用箭头函数来传递参数。在箭头函数中,可以访问事件对象(入event)以及传递给事件监听的其他参数

{/* 函数定义 */}
const handleClick2 =(aaa :Number) =>{console.log('clicked',aaa)return aaa
}或者function handleClick2 (aaa :Number){console.log('clicked',aaa)return aaa
}return (<button onClick={() => handleClick2(2)}>点击传值</button>
)

使用bind方法:

通过bind方法,可以绑定参数并创建一个新的函数,该函数将在事件触发是被调用

{/* 函数定义 */}
const handleClick2 =(aaa :Number) =>{console.log('clicked',aaa)return aaa
}或者function handleClick2 (aaa :Number){console.log('clicked',aaa)return aaa
}return (<button onClick={handleClick2.bind(null,2)}>点击传值</button>
)
错误陷阱
错误1

传递事件处理函数的函数应该是直接传递,而非直接调用。

这个示例中,handleClick 作为一个 onClick 事件处理函数传递。这会让React 记住,并且只在点击按钮的时候调用 传递的函数。

// 传递一个函数(正确写法)
<button onClick={handleClick}></button>// 调用一个函数(错误写法)
<button onClick={handleClick()}></button>
错误2
// 传递一个函数(正确)【alert 定义内联事件函数,点击的时候触发】
<button onClick="{() => alert('...')}"></button>// 调用一个函数(错误)【这个 alert 在组件渲染时触发,还不是在点击时触发】
<button onClick="{alert('...')}"></button>

其他常见响应事件

1、onChange 表单元素值发生变化触发

当表单元素的值发生变化时触发,比如输入框的文本内容发生变化。

import React,{useState} from 'react';function App() {const [person, setPerson] = useState({name:'',age:0})function inputUpChange (event: React.ChangeEvent<HTMLInputElement>){const {name,value} = event.targetconsole.log(name,value) //打印 nanme属性名,value 输入值setPerson({...person, [name]: value}) //设置值}return (<div><h4>当前信息{JSON.stringify(person)}</h4><input type="text" name='name' value={person.name} onChange={inputUpChange} /><input type="text" name='age' value={person.age} onChange={inputUpChange} /></div>);
}
2、onSubmit 表单提交时触发

点击按钮时触发 form表单 提交函数 submitUserInfo

注意:在表单上使用 onSubmit 事件,并没有阻止默认行为,它将触发表单的默认提交行为,导致页面刷新。

import React,{useState} from 'react';function App() {const [person, setPerson] = useState({name:'',age:0})function submitUserInfo(){console.log('submit',user)}return (<div><h4>当时onSubmit 信息</h4><form onSubmit={submitUserInfo}><input type="text" name='name' value={person.name} onChange={inputUpChange} /><button type='submit'>点击触发submit事件</button></form></div>);
}

为了阻止默认行为,可以在onSubmit 事件处理函数中调用 event.preventDefault() 方法。将阻止表单的默认提交行为,从而避免页面刷新。

下面的代码,在用户点击提交按钮时候,submitHandle 函数将被调用,并且 e.preventDefault() 将阻止表单的默认提交行为,从而避免页面刷新。可以在 submitHandle 函数中执行提交表单的逻辑。

在没有使用 preventDefault 的情况下,打印的对象和数组无法展开的,因为在提交后表单的默认行为会导致刷新

import React,{useState} from 'react';const initialList = [{ id: 0, title: 'Big Bellies', seen: false },{ id: 1, title: 'Lunar Landscape', seen: false },{ id: 2, title: 'Terracotta Army', seen: true },
]
function StatesFormBox() {const [iibb,setIibb] = useState(initialList)function submitHandle(e: any){e.preventDefault()console.log(e)console.log(666,iibb)}function aa(){setIibb([{ id: 3, title: 'Terracotta Army', seen: false }])}return (<div>{/* <h1>State Form</h1> */}<form onSubmit={submitHandle}><button type='submit'>点击按钮提交</button></form></div>)
}export default StatesFormBox;
3、onMouseEnter 当鼠标移入元素时触发
import React,{useState} from 'react';function App() {function handleMouseEnter(){console.log('鼠标移入元素了')}return (<div style={{width:'100px',height:'100px',border:'1px solid green'}} onMouseEnter={handleMouseEnter}><h4>鼠标进入</h4></div>);
}
4、onMouseLeave 当鼠标移出元素时触发
import React,{useState} from 'react';function App() {function handleMouseLeave(){console.log('鼠标移出元素了')}return (<div style={{width:'100px',height:'100px',border:'1px solid green'}} onMouseLeave={handleMouseLeave}><h4>鼠标移出</h4></div>);
}
5、onKeyDown 当按下键盘上的任何信息时触发

使用方法更上面类型

<input type="text" onKeyDown={handleKeyDown} />
6、onKeyUp 当释放键盘上的任意键时触发
<input type="text" onKeyUp={handleKeyUp} />
7、onFocus 当元素获取焦点时触发
<input type="text" onFocus={handleFocus} />
8、onBlur 当元素失去焦点时触发
<input type="text" onBlur={handleBlur} />
9、onScroll 当前元素滚动时触发
<div onScroll={handleScroll}>滚动时触发</div>

子组件接收父组件的child,类型vue的v-text

import React,{useState} from 'react';
import Gallery from './Gallery';function App() {function clickHandle(num: number, num2: number){console.log(num + num2)return num + num2}function MyButton4({onClick, children}:{onClick:(num:number,num2:number)=>number,children:string}){return (<div>{/* 渲染会显示 <button onClick={()=> onClick(2,2)}>我是传递</button> 的按钮 */}<button onClick={()=> onClick(2,2)}>{children}</button></div>)}return (<div><MyButton4 onClick={clickHandle}>我是传递</MyButton4></div>);
}

更新界面

就是更改数据,更新视图,数据驱动视图

1、从useState 中获得两样东西:当前的state(count),以及更新值的函数(setCount)。也可以起任何名字,但是惯例会像这样:[something, setSomething] 这样命名

2、第一次显示,count 的值默认为0,因为你

import React from 'react';
import { useState } from 'react';
优化后一行搞到:
import React,{useState} from 'react';function App() {{/* 从useState 中获得两样东西:当前的state(count),以及更新值的函数(setCount)。 */}const [count, setCount] = useState(0); {/* 默认值0*/}{/* 自定义命名 */}// 声明一个num的状态变量,并初始化为 2const [num, setNum] = useState(2); {/* 默认值2 */}function updateClick(){setCount(count + 1)}function updateNum(){setNum(num + 1)}return (<div className="App"><button onClick={updateClick}>点击 {count} 了</button><button onClick={updateNum}>点击了num值{num}了</button></div>);
}

Hook

再React中,以 use 开头的函数都被称为 Hook。 useState 是React 提供的内置 Hook 函数。

Hook 比普通函数更为严格。只能在组件(或者其他Hook)的顶层 调用 Hook。如果要在一个条件或者循环中使用 useState,需要在新的组件并在内部使用它。

注意

Hooks ---->以 use 开头的函数 -----> 只能在组件活自定义 Hook的最顶部调用。 不能在条件语句、循环语句或者其他嵌套函数内调用 Hook。Hook是函数,但将其视为关于组件需求的无条件声明。

//useState 的唯一参数是 state 变量的 初始值。在这个例子中,index的初始值被 useState(0) 设置为0
const [index, setIndex] = useState(0)
渲染步骤
  1. **组件进行第一次渲染。**因为你将 0 作为 index 的初始值传递给 useState ,它将返回 [0, setIndex] 。React 记住 0 是最新的 state值。
  2. 你更新了state。 当点击按钮时,调用 setIndex(index +1)index0 ,所以它是 setIndex(1) 。这告诉 React 现在记住 index1 并触发下一次渲染。
  3. 组件进行第二次渲染。 React 仍然看到 useState(0), 但是因为 React 记住了你将 index 设置为了 1 ,它将返回 [1, setIndex]
  4. 以此类推

组件共享

在子组件里使用父组件传的方法和变量数据

import React,{useState} from 'react';
import Gallery from './Gallery';// {person,size} 组件使用时的属性,要一一对应,对接收的值类型验证
function MyButton1({person,size}:{person:Object,size:number}) {return (<button>按钮一号:{JSON.stringify(person)}---{size > 1 ? 1 : 2}</button>)
}
// onClick 是组件使用的属性名,冒红后面的对象是对这个函数的描述的类型解析
function MyButton2({onClick}:{onClick:() => void}) {return (<button onClick={onClick}>按钮二{num}号</button>)
}
//接收一个属性,对函数执行的时候参数和返回值的要去
function MyButton3({onClick}:{onClick:(num:number,num2:number)=>number}){return (<div><button onClick={()=> onClick(2,2)}>按钮三</button></div>)
}function App() {const [num, setNum] = useState(2);return (<div><MyButton1 person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}size={100} /><MyButton2 onClick={updateNum} /><MyButton3 onClick={clickHandle} /></div>);
}

组件

定义组件

function Profile(){return (<imgsrc='https://www......'/>)
}

组件的导入导出

export 居然导出,export default 默认导出

function Profile(){return (<imgsrc='https://www......'/>)
}
// export 居然导出
export default function Gellery(){return (<h1>open111</h1><Profile />)
}

import 组件 from ‘组件文件地址’ =》 import Gallery from ‘./Gallery’

Gallery.tsx 导出
function Profile() {return (<imgsrc="https://i.imgur.com/QIrZWGIs.jpg"alt="Alan L. Hart"/>);}export default function Gallery(){return (<><h1>开始了</h1><Profile /><Profile /></>)}
app.tsx 导入

引入过程中,import Gallery from ‘./Gallery’; ,后缀.jsx添加与否都能正常使用。

import Gallery from './Gallery';function App() {return (<div><Gallery /><Gallery /></div>);
}
导入导出注意点

从一个文件中导出和导入多个文件

//用具名方式导出
export function Profile(){//****
}
//接着,具名导入的方式,从文件到当前组件文件中(用大括号)
import {Profile} from './Gallery.tsx'//渲染
export default function App() {return <Profile />;
}
//用默认导出的方式
export default function Gallery() {return (<section><h1>了不起的科学家们</h1></section>);
}// 导入 默认导出的组件
import Gallery from './Gallery.tsx';//渲染
export default function App() {return <Gallery />;
}

嵌套组组件

组件里可以渲染其他组件,但是 请不要嵌套定义组件的定义。下面这段代码 非常慢,并且还会导致bug产生

export default function Gallery() {// 🔴 永远不要在组件中定义组件function Profile() {// ...}// ...
}

正常使用

export default function Gallery() {// ...
}// ✅ 在顶层声明组件
function Profile() {// ...
}

组件记忆:双向绑定

组件通常需要通过 交互更改屏幕上显示的呢绒。输入表单 应该更新输入字段,点击轮播图上的 “下一个”应该更改显示的图片,点击 “购买” 应该将商品放入购物车。组件需要 “记住” 某些东西:当前输入值、当前图片、购物车等。值React中,这种组件特有的记忆称为 state。

普通的变量的值改变时,更新变量的值时,组件没有出现数据驱动视图

普通变量无法驱动改变视图

点击按钮,变量的值更新了,但是视图没有变化。

注意

updateAgeHandle() 事件处理函数整个更新局部变量 age,有两个原因使得视图没有更新

1、**局部变量无法在多长渲染中持久保持。**当React 再次渲染这个组件时,会从头开始渲染,不会考虑之前对局部变量的任何更改。

2、**更改局部变量不会触发渲染。**React 没有意识到它需要使用新数据再次渲染组件。

function App() {const user = {name: 'Joe',age: 32,}function updateAgeHandle(){user.age += 1console.log('age',user.age)}return (<div><h4>当前年龄:{user.age}</h4><button onClick={updateAgeHandle}>更新年龄</button></div>);
}
方案

要使用新数据更新组件,需要做两件事

1、保留 渲染之间的数据。

2、触发 React 使用新数据来渲染组件(重新渲染)

解决

useState Hook 提供了这两个解决功能

1、State 变量 用于保存渲染间的数据。

2、State setter 函数 更新变量并触发 React 再次渲染。

实现
// 要添加 state 变量,先从文件顶部导入 useState
import {useState} form 'react'// 然后 将局部变量定义的代码换成state 变量
//替换后的 index 是一个state变量,setIndex 是对应的 setter 函数。
let index = 0;  【将其修改为】====>>>  const [index, setIndex] = useState(0) //初始变量0//函数中触发
function updateAgeHandle(){setIndex(index + 1)
}

State 是隔离且私有的

State 是屏幕上组件实例内部的状态。也就是说,**如果你渲染同一个组件两次,每次副本都会有完全隔离的 state!**其中一个组件的state不会音响另外一个。

State 定义对象

看了这么多state 变量的定义,还是不太明白对象数据驱动视图怎么弄。

如下实现 表单输入更新

import React,{useState} from 'react';function App() {const [person, setPerson] = useState({name:'',age:0})function inputUpChange (event: React.ChangeEvent<HTMLInputElement>){const {name,value} = event.targetconsole.log(name,value) //打印 nanme属性名,value 输入值setPerson({...person, [name]: value}) //设置值}return (<div><h4>当前信息{JSON.stringify(person)}</h4><input type="text" name='name' value={person.name} onChange={inputUpChange} /><input type="text" name='age' value={person.age} onChange={inputUpChange} /></div>);
}

state 中更新数组

当操作 React state 中数组是时,你需要避免使用左列的方法,而首选右列的方法

避免使用 (会改变原始数组)推荐使用 (会返回一个新数组)
添加元素pushunshiftconcat[...arr] 展开语法(例子)
删除元素popshiftsplicefilterslice(例子)
替换元素splicearr[i] = ... 赋值map(例子)
排序reversesort先将数组复制一份(例子)
添加元素
import React,{useState} from 'react'function ArrayDomState() {const [arr, setArr] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])function add1ArrHandle() {setArr([...arr,11])}function add2ArrHandle() {setArr(arr.concat(11))}return (<>{/* 添加元素 */}<div><p>array 数据:{JSON.stringify(arr)}</p><button onClick={add1ArrHandle}>state 扩展运算符[...arr] 添加 state 数组数据</button></div><div><p>array 数据:{JSON.stringify(arr)}</p><button onClick={add2ArrHandle}>使用 concat 添加 state 数组数据</button></div></>)
}export default ArrayDomState
删除元素
import React,{useState} from 'react'function ArrayDomState() {const [arr, setArr] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])function delete1ArrHandle() {const newArr = arr.filter((item) => item !== 1)setArr(newArr)}function delete2ArrHandle() {const newArr = [...arr]newArr.splice(1, 1);setArr(newArr)}return (<>{/* 删除元素 */}<div><p>arr 删除元素{JSON.stringify(arr)}</p><button onClick={delete1ArrHandle}>state 使用 filter 删除数组数据</button></div><div><p>arr 删除元素{JSON.stringify(arr)}</p><button onClick={delete2ArrHandle}>state 使用 splice 删除数组数据</button></div></>)
}export default ArrayDomState
转换数组

这种方式就是转换数组,就是使用 setState() 方法来更新组件的state,从而实现对数据的转换操作。

const newArr = [...arr]
newArr.splice(1, 1);// 使用新的数组进行重渲染
setArr(newArr)
替换数组中的元素
import React,{useState} from 'react'function ArrayDomState() {const [arrs, setArrs] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])function arraysDomHandle() {const updateArr = arrs.map((item) => {if(item === 3){return 333333333} else {return item}});setArrs(updateArr)}return (<div><div><p>array 数据:{JSON.stringify(arrs)}</p><button onClick={arraysDomHandle}>点击替换数组中的元素</button></div></div>)
}export default ArrayDomState
向数组中间插入元素

向数组特定位置插入一个元素,这个位置既不在数组开头也不在数组末尾。

import React,{useState} from 'react'function ArrayDomState() const [arrs, setArrs] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])function insertArrHandle(){const insertIndex = 5;const newArr = [...arrs.slice(0, insertIndex),333333333,...arrs.slice(insertIndex)]setArrs(newArr)}return (<div>{/* 向数组中插入元素 */}<div><p>array 数据:{JSON.stringify(arrs)}</p><button onClick={insertArrHandle}>点击向数组中插入元素</button></div></div>)
}export default ArrayDomState
其他更改数组的情况

总有些事情,是仅靠展开运算符和 map() 或者 filter() 等不会直接修改原值的方法能做到的。例如翻转数组,或者数组排序,而 javaScript 中的 reverse() 和 sort() 方法会改变原数组,所以不能直接使用她们。

解决:先拷贝这个数组,然后再改变拷贝数组的值。

import React,{useState} from 'react'function ArrayDomState() const [arrs, setArrs] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])function changeArrHandle(){const newArr = [...arrs];newArr[0] = 333333333;newArr.reverse()setArrs(newArr)}return (<div>{/* 其他改变数组的情况 */}<div><p>array 数据:{JSON.stringify(arrs)}</p><button onClick={changeArrHandle}>点击改变数组中的元素</button></div></div>)
}export default ArrayDomState
问题:

在上面的代码中,虽然使用 [...arrs] 展开运算符创建了一份数组的拷贝值。当有了拷贝值后,就可以使用 newArr.reverse() 或者 newArr.sort() 这样修改原数组的方法。 甚至可以通过 newArr[0] = 333333333 这样的方式对特定元素进行赋值。

但是,这种拷贝方式,只能适用于 基础类型的数组,不适用对象数组的元素。 原因大家应该都知道,这种解构的方式是浅拷贝,新数组种的对象依然与原始对象数组的原始的内存地址。因此,如果你修改了拷贝数组内部的某个对象。

//虽然 nextList 和 list 是两个不同的数组,nextList[0] 和 list[0] 却指向了同一个对象。因此,通过改变 nextList[0].name,list[0].name 的值也会被改变const nextList = [...list]
nextList[0].name = 'tom'
setList(nextList)
更新数组对象的元素
import React,{useState} from 'react'const initialList = [{ id: 0, title: 'Big Bellies', seen: false },{ id: 1, title: 'Lunar Landscape', seen: false },{ id: 2, title: 'Terracotta Army', seen: true },
]function ArrayDomState() {const [myList, setMyList] = useState(initialList)function handleToggleMyList(artworkId :number, nextSeen: any) {setMyList(myList.map((item) => {if(item.id === artworkId){// 创建包含变更的 新对象return {...item, title: nextSeen}}else {return item}}))}return (<div>{/* 更新数组内部对象的值 */}<div><p>更新数据{JSON.stringify(myList)}</p><button onClick={() => handleToggleMyList(1,'修改咯')}>更新对象数组</button></div></div>)
}export default ArrayDomState
使用Immer 编写简单的更新
import React,{useState} from 'react'
import { useImmer} from 'use-immer'const initialList = [{ id: 0, title: 'Big Bellies', seen: false },{ id: 1, title: 'Lunar Landscape', seen: false },{ id: 2, title: 'Terracotta Army', seen: true },
]function ArrayDomState() {const [myList2, setMyList2] = useImmer(initialList)function handleToggleMyList2(artworkId :number, nextSeen: any) {setMyList2(draft => {console.log('draft',JSON.stringify(draft))const artwork = draft.find((item) => item.id === artworkId) console.log('artwork', JSON.stringify(artwork))if(artwork) artwork.title = nextSeenconsole.log('artwork222',JSON.stringify(artwork))})}return (<div>{/* 使用Immer 编写更加简洁的更新逻辑 */}<div><p>使用 Immer 编写:{JSON.stringify(myList2)}</p><button onClick={() => handleToggleMyList2(2,'Immer')}>使用Immer点击</button></div></div>)
}export default ArrayDomState

想修改对象数组的值,还得先拷贝一份。

使用Immer 时,类似 artwork.seen = nextSeen 这种会产生 mutation的语法不会再有任何问题了:

updateMyTodos(draft => {const artwork = draft.find(a => a.id === artworkId);artwork.seen = nextSeen;
});

状态管理

React 状态管理是指在React 应用中有效地管理和共享组件之间的状态。 React 也提供了一些内置的状态管理:例如 使用组件本地的状态( state) 和属性 ( props ),以及使用上下文 ( context )进行状态共享。

随着应用不断变大,应用变得更加复杂,这些内置的状态管理机制可能会变得不够灵活或难以维护。沉余或者重复的状态往往是缺陷的根源。 为了解决这个问题,通常会使用 ReduxMobx 或者 React Context API

使用State 状态相应输入

在react种,不用直接从代码层面上修改UI,不用编写诸如 “禁用按钮”、“启用按钮”、“显示成功消息” 等命令。只需要描述组件在不同状态(“初始状态”、“输入状态”、“成功状态”)下希望展示的UI,然后根据用户输入触发状态变更。

使用React 编写的反馈表单,根据 status 这个状态变量来决定显示提交按钮以及 是否显示成功消息

import React,{useState} from 'react';
function StatesFormBox() {const [age, setAge] = useState('')const [error, setError] = useState('')const [status, setStatus] = useState('typind')if(status === 'success'){return <div>Success</div>}async function submitHandle(e: any){e.preventDefault()setStatus('loading')try {await submitForm(age)setStatus('success')} catch(error) {console.log(error)setStatus('typind')setError('error')}}function setAgeHandle(e:any){setAge(e.target.value)}return (<div>{/* <h1>State Form</h1> */}<div>{age}==={error}----{status}</div><form onSubmit={submitHandle}><textarea value={age} onChange={setAgeHandle}></textarea><button type='submit'>点击按钮提交</button></form></div>)
}function submitForm(age: string){return new Promise((resolve,reject) => {setTimeout(() => {console.log(age,age === '18')if(age !== '18') {reject(new Error('年龄不正确'))}else{resolve('提交成功')}},2000)})
}export default StatesFormBox;

在组件共享状态

import React,{useState} from 'react';function Panel({title,children,isActive,onShow}:{title:string,children:string,isActive:boolean,onShow:()=>void}){return (<><h3>{title}</h3>{isActive ? (<p>children</p>) :(<button onClick={onShow}>显示</button>)}</> )
}function StatesFormBoxShare() {const [activeIndex, setActiveIndex] = useState(0)function setActiveHandle(value:number){setActiveIndex(value)}return(<><Panel title='标题' isActive={activeIndex === 0} onShow={() => setActiveHandle(0)}>112313</Panel><Panel title='标题二' isActive={activeIndex === 1} onShow={() => setActiveHandle(1)}>22222</Panel></>)
}export default StatesFormBoxShare

useReducer 的使用

在hooks中提供了 useReducer 功能,可以增强 ReducerDemo 函数提供类似 Redux的的功能。

useReducer 能接受一个 reducer 函数 作为参数,reducer 接受两个参数,一个是state 另外一个是action。然后返回一个状态 count 和 dispath,count 是返回状态中的值,而 dispath 是一个可以发布事件来更新 state 的。

基本使用
import React,{useReducer} from 'react'export default function ReducerDemo() {const [count, dispath] = useReducer((state,action)=> {//...}, 0);return (<div><h1 className="title">{count}</h1></div>)
}
要点

reducers 不应该包含异步请求、定时器、或者任何副作用(对组件外部有影响的操作),应该以不可变值的方式去更新对象和数组

修改对象

下面就是useReducer 更新的使用。

事件处理程序只通过派发 action 来 指定 发生了什么,而 reducer 函数通过 响应 actions 来决定 状态如何更新

import React,{useReducer} from 'react'//(1)初始的数据
const initInfoData = {name : '张三',age : 18,sex : '男',
}//(2) 定义组件
function UseReducerBox (){//(3) useReducer 接受一个reducer参数:【reducerFun 自定义函数,自定义的这个reducer函数reducerFun 接受两个参数:一个是state 另一个是action。】;useReducer 接受的第二个参数:【initInfoData 就是初始的 state 数据,就是初始数据】// useReducer 返回一个状态 count:【userInfo】和 dispath:【setUserInfo】,userInfo 是返回状态中的值,而 setUserInfo 是一个可以发布事件来触发更新state的// count 和dispath 是官方示例的命名const [userInfo, setUserInfo] = useReducer(reducerFun,initInfoData)//(7) 点击函数 触发 发布事件来更新state 的。function handleClick(){//触发发布更新后,useReducer 第一个参数就会执行了。setUserInfo('edit')}//(5)定义组件return (<><h5>useReducer 修改对象</h5><div>{JSON.stringify(userInfo)}</div>{/*(6) 触发点击*/}<button onClick={() => handleClick()}>点击设置</button></>)
}//(3) 定义 useReducer 的第一个reducer参数,接收两个参数 一个是 state 一个是action 
function reducerFun(state :any, action :any){//(4) state 当前状态下的数据,action为接收 setUserInfo 这个更新state的参数console.log(state, action) // 打印:{name: '张三', age: 18, sex: '男'} 'edit'//这个if可以不用if(action === 'edit'){//返回修改后状态数据return {name : '李四',age : 20,sex : '女'}}
}export default UseReducerBox
useState 和 useReducer 的对比
代码体积

通常,在使用 useState 的时候,开始的时候只需要写少量的代码。 而 useReducer 必须提前编写 reducer 函数和需要调度的 actions。 但是在多个事件处理程序以相似的方式修改 state 的时候, useReducer 可以减少代码量。

可读性:

状态更新逻辑足够简单的时候 useState 的可读性还可以,但是一旦逻辑变动复杂起来,就会使得代码变得臃肿难以阅读。这种情况下,useReducer 可以将状态更新逻辑和 事件处理程序分离。

可调试性:

使用 useState 出现问题,必须单步执行更多代码。而使用 useReducer 的时候,可以在 reducer 函数中通过打印日志的方式来观察每个状态的更新,以及为什么更新(来自哪个action)。如果所有的action都没问题,就知道问题出在 reducer本身的逻辑了。

区分:

useState 是React 中 最简单的状态管理方法。使用简单的对象来存储状态,并提供两个方法来访问和更新状态

useReducer 提供了一种更加复杂的状态管理方法。使用一个reducer 函数来处理状态更新,并提供一个 dispatch() 方法来触发状态更新。

useState 关键区别在于如何处理状态更新。 useState 使用简单的对象来存储状态。这使得它很好使用,但也可能导致性能问题,因为每次状态更新都会重新渲染组件。

useReducer 使用一个reducer函数 来处理状态更新。这使得可以更有效地处理复杂的状态更新,因为可以避免不必要的重新渲染。但是 useReducer 也更复杂,需要更多的学习和理解才能使用。

大多数情况下 useState 是足够来管理简单的状态。但是,如果需要处理复杂的状态更新,则 useReducer 可能是更好的选择

实验Immer 简化 reducers

这与平常的 state 中 修改对象和数组一样,可以使用Immer 库来简化 reducer。useImmerReducer 让可以通过 push 或者 arr[ i ] = 来修改state

import React,{} from 'react'
import { useImmerReducer } from 'use-immer'const initInfoData = {name : '张三',age : 18,sex : '男',
}function ImmerReducerBox (){const [userInfo, setUserInfo] = useImmerReducer(reducerFun,initInfoData)function handleClick(obj: any){setUserInfo(obj)}return (<><h5>ImmerReducerBox 简化 useReducer 对象</h5><div>{JSON.stringify(userInfo)}</div><button onClick={() => handleClick({type:'setName',payload:'大豆'})}>点击设置姓名</button><button onClick={() => handleClick({type:'setAge',payload:'19'})}>点击设置年龄</button><button onClick={() => handleClick({type:'setSex',payload:'女'})}>点击设置性别</button></>)
}function reducerFun(draft: any,action :any){switch(action.type){case 'setName':draft.name = action.payloadbreakcase 'setAge':draft.age = action.payloadbreakcase 'setSex':draft.sex = action.payloadbreakdefault:break}}export default ImmerReducerBox

使用 Context 深层次传递参数

描述

通常使用 props 将信息从父组件传递到子组件。但是,如果必须通过许多中间件向下传递props,或者在应用中的许多组件需要相同信息,传递props 会变得十分冗长和不变。Context 允许父组件向其下层无论多深的任何组件提供信息,无需通过props 显示传递

props 传递带来的问题

props 传递 是将数据通过 UI 树显式传递到 子组件的好方法。

但是当需要在组件树深层递参数以及需要在组件间复用相同的参数时,传递 props 就会变得很麻烦。最近的根节点的父组件可能离需要的组件很远,状态提升 到太高的层级会导致 逐层传递 props 的情况

在这里插入图片描述

Context的基本使用
import React,{createContext,useContext} from 'react'const initInfoData = {name : '张三',age : 18,sex : '男',
}
function Childrens() {return (<><div>第一个子元素</div><Childrens2 /></>)
}
function Childrens2() {const aaa = useContext(conTextS)return (<div>第二个子元素{JSON.stringify(aaa)}</div>)
}const conTextS = createContext(initInfoData)
function ContextBox (){return (<><div>1232222</div><Childrens /></>)
}export default ContextBox

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/210249.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Vue框架的电子商城购物平台小程序的设计与开发

基于JavaWebSSMVue电子商城购物平台小程序系统的设计和实现 源码获取入口KaiTi 报告/Ren务书Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码获取入口 KaiTi 报告/Ren务书 一、选题的目的和意义 自从微信推出了微信小程序…

使用命令行移除VSAN中故障磁盘

原创作者&#xff1a;运维工程师 谢晋 使用命令行移除VSAN中故障磁盘 前提故障盘移除 前提 客户有套VSAN环境内有一台服务器的磁盘组出现了一块故障的数据盘&#xff0c;但该盘已经处于完全掉线状态&#xff0c;无法进行正常移除。如下图&#xff1a; 如果遇到这种情况&am…

P9 LinuxC 进程概述 终端启动的程序父进程是终端

前言 &#x1f3ac; 个人主页&#xff1a;ChenPi &#x1f43b;推荐专栏1: 《C_ChenPi的博客-CSDN博客》✨✨✨ &#x1f525; 推荐专栏2: 《Linux C应用编程&#xff08;概念类&#xff09;_ChenPi的博客-CSDN博客》✨✨✨ &#x1f6f8;推荐专栏3: ​​​​​​《链表_ChenP…

【1】一文读懂PyQt简介和环境搭建

目录 1. PyQt简介 1.1. Qt 1.2. PyQt 1.3. 关于PyQt和PySide 2. 通过pip安装PyQt5 3. 无法运行处理 4. VSCode配置PYQT插件 PyQt官网:Riverbank Computing | Introduction 1. PyQt简介 PyQt是一套Python的GUI开发框架,即图形用户界面开发框架。 Python中经常使用的GU…

FreeRTOS的内存管理方法(超详细)

内存管理 我们知道每次创建任务、队列、互斥锁、软件定时器、信号量或事件组时&#xff0c;RTOS 内核都需要 RAM &#xff0c; RAM 可以从 RTOS API 对象创建函数内的 RTOS 堆自动动态分配&#xff0c; 或者由应用程序编写者提供。 如果 RTOS 对象是动态创建的&#xff0c;那么…

Leetcode—2646.最小化旅行的价格总和【困难】

2023每日刷题&#xff08;五十三&#xff09; Leetcode—2646.最小化旅行的价格总和 算法思想 看灵神的 实现代码 class Solution { public:int minimumTotalPrice(int n, vector<vector<int>>& edges, vector<int>& price, vector<vector&l…

发现数学之美--微积分的起源和用途(一文搞懂微积分)

数学&#xff0c;改变世界的基石。微积分十九世纪的三大自然发现之一&#xff0c;迪卡尔建立了解析几何&#xff0c;把数与图结合在一起&#xff0c;微积分的发现与创立&#xff0c;是数学新的里程碑&#xff0c;解决了常规方法无法解决的问题&#xff0c;是一次伟大的革命。迪…

git安装和配置

git安装和配置 一、软件介绍 Git是一个免费开源的分布式版本控制系统&#xff0c;旨在快速高效地处理从小型到大型项目的所有内容。 Git易于学习&#xff0c;占地面积小&#xff0c;性能闪电般快。它以廉价的本地分支、方便的暂存区域和多个工作流等功能胜过了Subversion、C…

【论文阅读】Reachability and distance queries via 2-hop labels

Cohen E, Halperin E, Kaplan H, et al. Reachability and distance queries via 2-hop labels[J]. SIAM Journal on Computing, 2003, 32(5): 1338-1355. Abstract 图中的可达性和距离查询是许多应用的基础&#xff0c;从地理导航系统到互联网路由。其中一些应用程序涉及到巨…

金南瓜SECS/GEM C# SDK 快速使用指南

本文对如何使用金南瓜SECS/GEM C# SDK 快速创建一个满足SECS/GEM通信要求的应用程序&#xff0c;只需简单3步完成。 第一步&#xff1a;创建C# .NET程序 示例使用Visual Studio 2010&#xff0c;使用者可以选择更高级版本 Visual Studio 第二步&#xff1a;添加DLL库引用&am…

图论-并查集

并查集(Union-find Sets)是一种非常精巧而实用的数据结构,它主要用于处理一些不相交集合的合并问题.一些常见的用途有求连通子图,求最小生成树Kruskal算法和最近公共祖先(LCA)等. 并查集的基本操作主要有: .1.初始化 2.查询find 3.合并union 一般我们都会采用路径压缩 这样…

git标签的管理与思考

git 标签管理 git 如何打标签呢&#xff1f; 标签是什么? 标签 相当于一个 版本管理的一个贴纸&#xff0c;随时 可以通过标签 切换到 这个版本的状态 &#xff0c; 有人可能有疑问 git commit 就可以知道 代码的改动了&#xff0c; 为啥还需要标签来管理呢&#xff1f; …

node笔记

文章目录 一、Node.js基础1. 认识Node.js01 nodejs的特性02 使用 Node.js 需要了解多少 JavaScript03 浏览器环境vs node环境 2. 开发环境搭建3. 模块、包、commonJS02 CommonJS规范03 modules模块化规范写法 4. Npm&Yarn01 npm的使用02 全局安装 nrm03 yarn使用 5. 内置模…

在idea中使用maven创建dynamic web project

1、先创建一个empty project 2、添加一个module , 核心是选择maven archetype webapp, 这个是maven提供的创建web工程的模版。 3、添加完等自动安装好即可 4、目录可能不完整 右键src---->点击New---->点击Directory &#xff08;注意&#xff1a;这是笔者所缺失的结…

每日一道c语言

任务描述 题目描述:输入10个互不相同的整数并保存在数组中&#xff0c;找到该最大元素并删除它&#xff0c;输出删除后的数组 相关知识&#xff08;略&#xff09; 编程要求 请仔细阅读右侧代码&#xff0c;结合相关知识&#xff0c;在Begin-End区域内进行代码补充&#xf…

ooTD I 女儿是自己的,尽情打扮尽情可爱

分享女宝的时尚穿搭 奶乎乎的黄色也太好看了 超足充绒量&#xff0b;优质面料 柔软蓬松上身体验感超赞 怎么穿都好看系列 轻轻松松打造时尚造型&#xff01;&#xff01;

Linux 删除文件名乱码的文件

现象&#xff1a; 处理&#xff1a; 1.>ls -li 获取文件对应的ID号 2.把删除指定文件&#xff08;ID号 &#xff09;执行&#xff1a; find ./ -inum 268648910 -exec rm {} \;

微信小程序_介绍

开发准备 注册微信小程序 进入微信公众平台 点击立即注册&#xff0c;选择小程序&#xff0c;前往注册 完善个人/企业信息 获取AppID 进入小程序页面->开发->开发设置->AppID 下载微信开发者工具 微信官方下载下载微信开发者工具稳定版 创建项目 绑定AppID不使用…

用Rust刷LeetCode之27 移除元素

27. 移除元素 难度: 简单 原描述: 新描述: func removeElement(nums []int, val int) int { for i : 0; i < len(nums); i { if nums[i] val { nums append(nums[:i], nums[i1:]...) i-- } } return len(nums)} Rust 版本 下面这种写法编译无法通过: pub fn remove_…

基于ssm平面设计课程在线学习平台系统源码和论文

idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 随着信息化时代的到来&#xff0c;管理系统都趋向于智能化、系统化&#xff0c;平面设计课程在线学习平台系统也不例外&#xff0c;但目前国内的市场仍都使用人工管理&#xff0c;市场规模越来越大&#xff0c;…