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>
      <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.mfbz.cn/a/234541.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

12.9文档记录——脱欧建模

4 脱欧对英国整体的影响 4.1 脱欧影响评估模型 4.1.1 指标的确定和数据的收集 为了建立指标体系&#xff0c;我们需要选择有代表性的指标。在有关脱欧的大量研究基础上[1]&#xff0c;考虑到脱欧对英国经济、民生、国际影响等各个方面影响&#xff0c;我们最终在兼顾模型有效…

表格的介绍与实战(详细且有案例)

目录​​​​​​​​​​​​​​ 表格的主要作用&#xff1a; 表格的基本语法&#xff1a; 表格相关的标签 合并单元格&#xff1a; 实战&#xff1a; 表格的主要作用&#xff1a; 表格主要是用来展示数据的&#xff0c;使用表格来展示数据&#xff0c;数据可读性更好…

大数据HCIE成神之路之数据预处理(1)——缺失值处理

缺失值处理 1.1 删除1.1.1 实验任务1.1.1.1 实验背景1.1.1.2 实验目标1.1.1.3 实验数据解析 1.1.2 实验思路1.1.3 实验操作步骤1.1.4 结果验证 1.2 填充1.2.1 实验任务1.2.1.1 实验背景1.2.1.2 实验目标1.2.1.3 实验数据解析 1.2.2 实验思路1.2.3 实验操作步骤1.2.4 结果验证 1…

基于Solr的全文检索系统的实现与应用

文章目录 一、概念1、什么是Solr2、与Lucene的比较区别1&#xff09;Lucene2&#xff09;Solr 二、Solr的安装与配置1、Solr的下载2、Solr的文件夹结构3、运行环境4、Solr整合tomcat1&#xff09;Solr Home与SolrCore2&#xff09;整合步骤 5、Solr管理后台1&#xff09;Dashbo…

关于图像清晰度、通透度的描述

1、问题背景 在图像评测过程中&#xff0c;从主观上一般怎么去评判一副图像的优劣呢&#xff1f; 比较显而易见的就是图像的清晰度和通透度&#xff0c;他们决定了评判者对画质的第一印象。 那怎么去理解图像的清晰度和通透度呢&#xff1f;这是本文要描述的内容。 2、问题分…

YOLOV3 SPP 目标检测项目(针对xml或者yolo标注的自定义数据集)

1. 目标检测的两种标注形式 项目下载地址:YOLOV3 SPP网络对自定义数据集的目标检测(标注方式包括xml或者yolo格式) 目标检测边界框的表现形式有两种: YOLO(txt) : 第一个为类别,后面四个为边界框,x,y中心点坐标以及h,w的相对值 xml文件:类似于网页的标注文件,里面会…

Element-ui框架完成vue2项目的vuex的增删改查

看效果图是否是你需要的 这是原来没有Element-ui框架的 首先&#xff0c;你要在你的项目里安装Element-ui yarn命令 yarn add element-uinpm命令 npm install element-ui --save好了现在可以粘贴代码 //main.js import Vue from vue import Vuex from vuex import VueRouter …

跟着chatgpt一起学|2.Clickhouse入门(2)

跟着chatgpt一起学|2.clickhouse入门&#xff08;1&#xff09;-CSDN博客 chatgpt规划的学习路径如下&#xff1a; 目录 2.数据建模和表设计 2.1 数据模型和表设计原则 2.1.1 什么是LowCardinality类型&#xff1f; 2.1.2 什么是数据分片&#xff1f; 2.2 ClickHouse支持…

初学者如何入门 Generative AI 之 Stable Diffusion 与 CLIP :看两篇综述,玩几个应用感受一下先!超多高清大图,沉浸式体验

文章大纲 4种 图片生成 的算法扩散模型的起源Stable DiffusionCLIP参考文献与学习路径A synthography of an astronaut riding a horse created in NightCafe Studio with Stable Diffusion XL (SDXL). Prompt is a photograph of an astronaut riding a horse with weight of …

数据结构与算法-Rust 版读书笔记-1语言入门

数据结构与算法-Rust 版笔记 一、语言入门 1、关键字、注释、命名风格 目前&#xff08;可能还会增加&#xff09;39个&#xff0c;注意&#xff0c;Self和self是两个关键字。 Self enum match super as extern mod trait async false …

【LeetCode热题100】【滑动窗口】无重复字符的最长子串

给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: 输入: s "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc"&#xff0c;所以其长度为 3。示例 2: 输入: s "bbbbb" 输出: 1 解释: 因为无…

macos下安装科研绘图软件Origin

科研人必备软件Origin&#xff0c;主要是考虑到很多期刊都要求绘制origin可编辑的图&#xff0c;所以有些时候必须用这个软件&#xff0c;但是这个软件macos并不支持&#xff0c;所以必须考虑其他的方案&#xff0c;我没有安装虚拟机&#xff0c;而是使用crossover 安装crosso…

程序的机器代码表示--函数调用

call和ret指令 如何访问栈帧、如何切换栈帧、如何传递参数和返回值 call、ret指令作用&#xff1a; call&#xff1a;1&#xff09;将IP&#xff08;即PC&#xff09;旧值压栈保存&#xff08;保存在函数的栈帧顶部&#xff09;&#xff1b;2&#xff09;设置IP新值&#xff0…

P1317 低洼地题解

题目 一组数&#xff0c;分别表示地平线的高度变化。高度值为整数&#xff0c;相邻高度用直线连接。找出并统计有多少个可能积水的低洼地&#xff1f; 如图&#xff1a;地高变化为 [0,1,0,2,1,2,0,0,2,0]。 输入输出格式 输入格式 两行&#xff0c;第一行n, 表示有n个数。第…

改进的A*算法的路径规划(1)

引言 近年来&#xff0c;随着智能时代的到来&#xff0c;路径规划技术飞快发展&#xff0c;已经形成了一套较为 成熟的理论体系。其经典规划算法包括 Dijkstra 算法、A*算法、D*算法、Field D* 算法等&#xff0c;然而传统的路径规划算法在复杂的场景的表现并不如人意&#xff…

【动态规划】斐波那契数列模型_解码方法_C++(medium)

题目链接&#xff1a;leetcode解码方法 目录 题目解析&#xff1a; 算法原理 1.状态表示 2.状态转移方程 3.初始化 4.填表顺序 5.返回值 编写代码 题目解析&#xff1a; 题目让我们求解码 方法的 总数 由题可得&#xff1a; 0和有前导0&#xff08;比如06、08、04&am…

(企业项目)微服务项目解决跨域问题:

前后端分离项目中前端出现了跨域的问题 在网关模块配置文件中添加 配置 application.properties # 允许请求来源&#xff08;老版本叫allowedOrigin&#xff09; spring.cloud.gateway.globalcors.cors-configurations.[/**].allowedOriginPatterns* # 允许携带的头信息 spri…

vue 实现返回顶部功能-指定盒子滚动区域

vue 实现返回顶部功能-指定盒子滚动区域 html代码css代码返回顶部显示/隐藏返回标志 html代码 <a-icontype"vertical-align-top"class"top"name"back-top"click"backTop"v-if"btnFlag"/>css代码 .top {height: 35px;…

RabbitMQ学习笔记10 综合实战 实现新商家规定时间内上架商品检查

配置文件&#xff1a; 记住添加这个。 加上这段代码&#xff0c;可以自动创建队列和交换机以及绑定关系。 我们看到了我们创建的死信交换机和普通队列。 我们可以看到我们队列下面绑定的交换机。 我们创建一个controller包进行测试: 启动&#xff1a; 过一段时间会变成死信队列…

JVM虚拟机系统性学习-类加载子系统

类加载子系统 类加载的时机 类加载的时机主要有 4 个&#xff1a; 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时&#xff0c;如果对应的类没有初始化&#xff0c;则要先进行初始化 new 关键字创建对象时读取或设置一个类型的静态字段时&#xff08;被 …
最新文章