React基础语法整理

news2024/11/19 14:31:13
安装:
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>
      <img
        className="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>
      <img
        className="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.target
      console.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的状态变量,并初始化为 2
  const [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 (
    	<img
            src='https://www......'
         />
    )
}

组件的导入导出

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

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

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

Gallery.tsx 导出
function Profile() {
    return (
      <img
        src="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 += 1
    console.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.target
      console.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 = nextSeen
            console.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.payload
            break
        case 'setAge':
            draft.age = action.payload
            break
        case 'setSex':
            draft.sex = action.payload
            break
        default:
            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.coloradmin.cn/o/1298237.html

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

相关文章

【C++】输入输出流 ⑤ ( cin 输入流对象 | cin.ignore() 函数 | cin.peek() 函数 | cin.putback() 函数 )

文章目录 一、cin.ignore() 函数1、cin.ignore() 函数简介2、cin.ignore() 函数原型3、代码示例 - cin.ignore() 函数 二、cin.peek() 函数1、cin.peek() 函数简介2、代码示例 - cin.peek() 三、cin.putback() 函数1、cin.putback() 函数简介2、代码示例 - cin.putback() 一、c…

MEME成风,为何比特币生态无法复刻以太坊生态的多样玩法?

铭文市场火了之后&#xff0c;很多人对 BTC L2 投入了过多的期许&#xff0c;认为 BTC 2 层会像以太坊 layer2 一样辉煌&#xff1f; 然而事实是&#xff0c;比特币生态的「成功」可能很长时间会停滞在「资产发行」叙事阶段&#xff0c;要复刻以太坊的生态多样玩法&#xff0c…

thinkphp6入门(12)-- 一对一关联模型

定义一对一关联&#xff0c;例如&#xff0c;一个用户都有一个个人资料。 一、两表 1.用户表:user 2.工作经验表&#xff1a;work_experience user表的id关联work_experience表的user_id。 注意看&#xff0c;user_id1的有2条工作经验 二、数据模型 主表模型&#xff1a;…

低多边形3D建模动画风格纹理贴图

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 当谈到游戏角色的3D模型风格时&#xff0c;有几种不同的风格&#xf…

掌握PyTorch数据预处理(一):让模型表现更上一层楼!!!

引言 在PyTorch中&#xff0c;数据预处理是模型训练过程中不可或缺的一环。通过精心优化数据&#xff0c;我们能够确保模型在训练时能够更高效地学习&#xff0c;从而在实际应用中达到更好的性能。今天&#xff0c;我们将深入探讨一些常用的PyTorch数据预处理技巧&#xff0c;…

【交换排序 简单选择排序 堆排序 归并排序】

文章目录 交换排序简单选择排序堆排序归并排序 交换排序 冒泡排序的算法分析&#xff1a; 冒泡排序最好的时间复杂度是O&#xff08;n&#xff09;冒泡排序最好的时间复杂度是O&#xff08;n平方&#xff09;冒泡排序平均时间复杂度为O&#xff08;n的平方&#xff09;冒泡排…

1-6、编程语言排行榜

语雀原文链接 https://www.tiobe.com/tiobe-index/

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

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

如何自动筛选高质量的指令微调数据喂给大模型?

前言 大家都知道在大模型时代&#xff0c;数据有多么重要&#xff0c;质量多高都不过分&#xff01;甚至直接决定着最终的效果。 尤其做SFT&#xff0c;模型框架基本不用改&#xff08;事实上也改不动&#xff09;&#xff0c;如何做一份符合自己场景高质量的SFT数据成了唯一…

C++中STL的容器vector

文章目录 什么是vectorvector与普通顺序表不同的点 vector的成员函数operatoroperator[]begin与end与iteratorsize()capacityresizeemptyreservepush_backpop_backinserteraseswapclear成员变量 总结 什么是vector vector&#xff1a;是数据结构里面的顺序表&#xff0c;开辟一…

【MATLAB】基于EMD分解的信号去噪算法(基础版)

代码操作 【MATLAB】基于EMD分解的信号去噪算法&#xff08;基础版&#xff09; 代码的主要内容 基于EMD&#xff08;经验模态分解&#xff09;的信号去噪算法通常可以结合相关系数、信号的熵值或者方差贡献率来完成去噪处理。这些指标可以用于确定阈值&#xff0c;从而对信号…

WPF仿网易云搭建笔记(2):组件化开发

文章目录 前言专栏和Gitee仓库依赖属性实战&#xff1a;缩小&#xff0c;全屏&#xff0c;关闭按钮依赖属性操作封装主窗口传递this本身给TitleView标题控件主要代码MainWindow.xmalMainWindow.cs依赖属性方法封装TitleView.csTitleViewModelTitleViewModel实现效果 前言 这次…

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

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

Mac虚拟机CrossOver23破解版下载和许可证下载

CrossOver Mac Mac 和 Windows 系统之间的兼容工具。使 Mac 操作系统的用户可以运行 Windows 系统的应用&#xff0c;从办公软件、实用工具、游戏到设计软件&#xff0c; 您都可以在 Mac 程序和 Windows 程序之间随意切换。 系统要求 运行macOS的基于Intel或Apple Silicon 的…

99、NeRF ray space

CG相机模型 在图形学中最常用的相机模型的原理和小孔成像是类似的。 不同之处在于&#xff0c;如上图&#xff0c;小孔成像得到的图像是倒立的&#xff0c;但是我们希望得到的图像是正向的&#xff0c;因此&#xff0c;我们选择小孔前成像。 从 3D 到 2D 的投影&#xff0c;…

笔迹鉴定系统

用于笔迹鉴定书的生成。对于检材进行的特征采集&#xff0c;将采集到的特征自动排版&#xff0c;生成比对表&#xff0c;然后在比对表上进行标注。主要标注有&#xff1a;写法、笔顺、箭头、实线、虚线、左括号、右括号、上括号、下括号、运笔、注释、来源、折线、测量、矩形、…

iptables——建立linux安全体系

目录 一. 安全技术类型 二. linux防火墙 1. 按保护范围划分&#xff1a; 2. 按实现方式划分&#xff1a; 3. 按网络协议划分&#xff1a; 4. 防火墙原理 三. 防火墙工具——iptables 1. netfilter 中五个勾子函数和报文流向 数据包传输过程&#xff1a; ① .五表四链…

【2023传智杯-新增场次】第六届传智杯程序设计挑战赛AB组-DEF题复盘解题分析详解【JavaPythonC++解题笔记】

本文仅为【2023传智杯-第二场】第六届传智杯程序设计挑战赛-题目解题分析详解的解题个人笔记,个人解题分析记录。 本文包含:第六届传智杯程序设计挑战赛题目、解题思路分析、解题代码、解题代码详解 文章目录 一.前言二.赛题题目D题题目-E题题目-F题题目-二.赛题题解D题题解-…

个人博客网站如何实现https重定向(301)到http

对于个人网站站注册比较少的&#xff0c;服务器配置不是很好的&#xff0c;没必要https,https跳转到http是要时间的&#xff0c;会影响网站打开的速度。免费的https每年都要更换。个人博客网站https有一段时间了&#xff0c;而且很多页面都有收录排名&#xff0c;现在已去掉htt…

redis中使用事务保护数据完整性

事务是指一个执行过程&#xff0c;要么全部执行成功&#xff0c;要么失败什么都不改变。不会存在一部分成功一部分失败的情况&#xff0c;也就是事务的ACID四大特性&#xff08;原子性、一致性、隔离性、持久性&#xff09;。但是redis中的事务并不是严格意义上的事务&#xff…