React_doc

发布时间 2023-09-19 16:13:13作者: Felix_Openmind

React =》 构建用户界面的JS库,用户界面是由按钮、文本和图像等小的单元内容构建。
React可以组合成可重用、可嵌套的组件。

组件案例

function Profile() {
  return (
    <img src='https://i.xxx.com/test.jpg' alt=''/>
  )
}

export default function Gallery() {
  return (
    <section>
      <h1>Amazing scientists</h1>
      <Profile/>
      <Profile/>
      <Profile/>
    </section>
  )
}

利用props传递数据给子组件

React组件会使用props来进行组件之间的通讯,每个父组件可以通过为子组件提供props的方式来传递信息
props =》 可以传递:对象、数组、函数、JSX等

function Card({ children }) {
  return (
     <div className='card'>
        {children}
     </div>
  )
}

function Avatar({person, size}) {
  return (
    <img 
      className='avatar'
      src={getImageUrl(person)}
      alt={person.name}
      width={size}
      height={size}/>
  )
}


export default function Profile() {
  return (
    <Card>
      <Avatar
        size={100}
        person={{
          name: 'T',
          imageId: '12asd',
        }}/>
    </Card>
  )
}

条件渲染

根据不同的条件来显示不同的东西,在React中,可使用JS语法,eg: if、&&、?:操作符实现有条件的渲染JSX

function Item({name, isPacked}) {
  return (
    <li className='item'>
      {name} {isPacked && '✔'}
    </li>
  )
}

export default function PackingList() {
  return (
    <section>
      <h1> Sally Ride's Packing List</h1>
      <ul>
        <Item isPacked={true} name='Space suit'/>
        <Item isPacked={true} name='Helmet with a golden leaf'/>
        <Item isPacked={false} name='Photo of Tam'/>
      </ul>
    </section>
  )
}

渲染列表

针对数据集合进行遍历,在React中使用filter()和map()实现数组的过滤和转换,将数据数组转换为组件数组;

对于数组的每个元素向,指定一个key;

import {people} from './data.js';
ipmort { getImageUrl } from './utils.js';

export default function List() {
  const listItems = people.map(person => 
    <li key={person.id}>
      <img src={getImageUrl(person)} alit=""/>
      <p>
        <b>{person.name}</b>
        {' ' + person.name + ' ' }
        known for { person.accomplishment }
      </p>
    </li>
};
return (
   <article>
    <h1>Scientist</h1>
    <ul>{listItems}</ul>
   </article>
)

Pure - Component

  • 只负责自己的任务,不会更改在该函数调用前已经存在的对象或变量
  • 输入相同,输出也相同:在输入相同的情况下,对纯函数来说总是返回相同的结果
let guest = 0;

function Cup() {
  // Bad: changing a preexisting variable
  guest = guest + 1;
  return <h2>Tea Cup for guest ${guest}</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup/>
      <Cup/>
      <Cup/>
    </>
  )
}

// 通过传递props使得组件变得纯粹,而非修改已经有的变量

function Cup({ guest }) {
  return <h2>Tea Cup for guest #{guest}</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup guest={1}/>
      <Cup guest={2}/>
      <Cup guest={3}/>
    </>
  )
}

在JSX中通过大括号使用JS

JSX允许在JS中编写类似HTML的标签,从而使得渲染的逻辑和内容可以融合在一起

  • JSX的大括号内引用JS变量
  • JSX的大括号内调用JS函数
  • JSX的大括号内使用JS对象
export default function Avatar() {
  const avatar = 'http://www.baidu.com';
  const desc == 'test ---------- ';
  const userName = 'Wangz';
  return (
    <span>userName: {userName}</span>
    <img className='avatar' src={avatar} alt={desc}/>
  )
}

// 使用“双大括号”:JSX中的CSS和对象
// 除了字符串、数字和其他JS表达式,甚至可以在JSX中传递对象
// 对象也是用大括号表示 eg: {name: "wangzz", age: 24}
export default function TodoList() {
  return (
    <ul style={{backgroundColor: 'black', color: 'pink'}}>
      <li> Improve the videophone</li>
      <li> Perpare ae</li>
      <li> Test</li>
    </ul>
  )
}

JSX小结:

  1. JSX引号内的值会作为字符串传递给属性
  2. 大括号可以将JS的逻辑和变量带入标签中
  3. 会在JSX标签中的内容区域或紧随属性的 = 后起到作用
  4. {{}}不会特殊语法: 只是包含在JSX大括号内的JS对象

将Props传递给组件

React组件会使用props互相同通信,每个父组件都可以提供props给子组件
从而将一些信息传递给子组件,props可传递对象、数组和函数

Props是不可变的,当一个组件需要改变其props的时候
将不得不请求其父组件传递不同的props —— 一个新对象,旧的props会被丢弃,
最终JS引擎会回收他们占用的内存;

不要尝试“更改props",当需要响应用户输入的时候,可以设置”state"可以在

添加交互

界面上的控件会根据用户的输入而更新,
点击按钮切换轮播图的显示,在React中,随着时间变化的数据称之为状态(state)
可以向任何组件添加状态,并按照需求进行更新。

响应事件

React允许向JSX中添加事件处理程序等,事件处理程序即函数,在用户交互的时候触发
eg: click、hover、focus、input、blur等

交互事件

界面控件根据用户的输入而更新,eg: 点击按钮切换轮播图的展示,在React中,随着时间变化的数据被称为状态(state)
可以向任何组件添加状态,并按照需求进行更新。

  • 响应事件
function Button({ onClick, children }) {
  return (
    <button onClick={onClick}>
      {children}
    </button>
  )
}


function Toobar({ onPlayMovie, onUploadImage }){
  return (
    <Button onClick={onPlayMovie}>
      Play Movie
    </Button>
    <Button onClick={onUploadImage}>
      Upload Image
    </Button>
  )
}

export default function App() {
  return (
    <Toolbar
       onPlayMovie={() => alert('Playing')}
       onUploadImage={() => alert('UP}
     />
  )
}

State: 组件的记忆

组件通常需要根据交互改变屏幕上的内容,在表单中键入更新输入栏
useState Hook为组件添加状态,Hook能够让组件使用React功能的特殊函数
useState声明一个状态变量,接收初始状态并返回一对值:当前状态以及一个更新状态的设置函数

const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);
  • example
import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);
  const hasNext = index < sculptureList.length - 1;
  
  function handleNextClick() {
    if (hasNext) {
      setIndex(index + 1);
    } else {
      setIndex(0);
    }
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

  let sculpture = sculpture[index];
  return (
    <>
      <button onClick={handleNextClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name}</i>
        by {sculpture.artist}
      </h2>
      <h3>
        ({index+1} of {sculptureList.length})
      </h3>
      <button onClick={handleMore Click}>  
        {showMore ? 'Hide' : 'show'} details
      </button>
      <img src={sculpture.url} alt={ssculpture.alt}/>
    </>
  )
}

渲染和提交

在组件显示在屏幕之前需要由React进行渲染

  1. 触发渲染(将订单送到厨房)
  2. 渲染组件(按照订单准备)
  3. 提交到DOM(将订单送到桌前)

快照的状态

和普通JS变量不同,React状态的行为更像一个快照,设置它不会改变已有的状态变量,而是触发一次重新渲染

console.log(count); // 0
setCount(count + 1); // 请求用1重新渲染
console.log(count); // 0

更新状态中的对象

状态可以持有任何类型的JS值,(对象),★无法直接改变在React状态中持有的对象和数组。
当需要更新一个对象和数组的时候,需要创建一个新的对象(或复制现有的对象)
用这个副本来更新状态。
... 展开语法来赋值想要改变的对象和数组

import { useState } from 'react'

export default function Form() {
  const [person, setPerson] = useState({

  });
}

什么是mutation?

在state中存放任意类型的JS值

const [x, setX] = useState(0);

setX(5); // 从0 =》 5,数字0本身没有变,JS中无法内置原始值 eg: 数字、字符串和布尔值进行更改

// state存放对象
const [position, setPosition] = useState({x: 0, y: 0});
// 可以改变对象自身的内容,但是会制造一个mutation
position.x = 5;

// 因此你应该替换它们的值,而不是对它们进行修改。

★ 将state视为只读的

应该将所有存放在state中的JS对象都视为只读的

...展开语法本质是是“浅拷贝” —— 只会复制一层,可以提高执行速度

  • eg: 使用一个事件处理函数来更新多个字段
import { useState }  from 'react'

export default function Form() {
  const [person, setPerson] = useState({
    firstName: 'Barbara',
    lastName: 'Hepworth',
    email: 'bheppasdfjpijpia',
  });

  function handleChange(e) {
    setPerson({...person, [e.target.name]: e.target.value});
  }

  return (
    <>
      <label>
        First name: <input/>
      
      </label>
    </>
  )
}

更新一个嵌套对象

const [person, setPerson] = useState({
    name: 'Niki',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
      image: 'www.baidu.com',
    }
});
// 将state视为不可变的,为了修改city的值,需要创建一个新的artwork对象
const nextArtwork = { ...person.artwork, city: 'NewDelhi'}
const nextPerson = { ...person, artwork: nextArtwork };
setPerson(nextPerson);

// or

setPerson({
    ...person, // 复制其他字段的数据
    artwork: {
        ...person.artwork, // 复制之前person.artwork中的数据
        city: 'New Delhi',
    }
});


// ★ 使用Immer编写简介的更新逻辑
// 可以使用Immer刘幸苦来实现更为便捷的改变嵌套展开效果

updatePerson(draft => {
  draft.artowork.city = 'Lagos';
});

// Immer提供的draft是一种特殊类型的对象,称之为Proxy
// 会记录所有的擦欧总,Immer会弄清楚draft对象的那些部分改变了,根据修改生成新对象

使用Immer

  1. npm install use-immer // 添加immer依赖
  2. import { useImmer } from 'use-immer' => 替换掉import { useState } from 'react'
import { useImmer } from 'use-immer';

export default function Form() {
  const [person, updatePerson] = useImmer({
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
      image: 'https://i.imgur.com/Sd1AgUOm.jpg',
    }
  });

  function handleNameChange(e) {
    updatePerson(draft => {
      draft.name = e.target.value;
    });
  }

  function handleTitleChange(e) {
    updatePerson(draft => {
      draft.artwork.title = e.target.value;
    });
  }

  function handleCityChange(e) {
    updatePerson(draft => {
      draft.artwork.city = e.target.value;
    });
  }

  function handleImageChange(e) {
    updatePerson(draft => {
      draft.artwork.image = e.target.value;
    });
  }

  return (
    <>
      <label>
        Name:
        <input
          value={person.name}
          onChange={handleNameChange}
        />
      </label>
      <label>
        Title:
        <input
          value={person.artwork.title}
          onChange={handleTitleChange}
        />
      </label>
      <label>
        City:
        <input
          value={person.artwork.city}
          onChange={handleCityChange}
        />
      </label>
      <label>
        Image:
        <input
          value={person.artwork.image}
          onChange={handleImageChange}
        />
      </label>
      <p>
        <i>{person.artwork.title}</i>
        {' by '}
        {person.name}
        <br />
        (located in {person.artwork.city})
      </p>
      <img 
        src={person.artwork.image} 
        alt={person.artwork.title}
      />
    </>
  );
}

事件处理函数变得更加简洁方便;随意在一个组件中同时使用useState和useImmer

为什么在React中无法推荐直接修改state?

将 React 中所有的 state 都视为不可直接修改的。
当你在 state 中存放对象时,直接修改对象并不会触发重渲染,并会改变前一次渲染“快照”中 state 的值。
不要直接修改一个对象,而要为它创建一个 新 版本,并通过把 state 设置成这个新版本来触发重新渲染。
你可以使用这样的 {...obj, something: 'newValue'} 对象展开语法来创建对象的拷贝。
对象的展开语法是浅层的:它的复制深度只有一层。
想要更新嵌套对象,你需要从你更新的位置开始自底向上为每一层都创建新的拷贝。
想要减少重复的拷贝代码,可以使用 Immer。

更新state中的数组

数组是另外一种存储在state中的JS对象,

slice => 拷贝数组或数组的一部分
splice => 会直接修改原始数组(插入或删除元素

数组正确修改方法

setArtists(
  [
    ...artists, // 新数组包含原数组的所有元素
    {id: nextId++, name: name} // 在末尾添加一个新的元素
  ]
);

从数组中删除元素 =》 filter或map来进行过滤

import { useState } from 'react';

let initialArtists = [
  { id: 0, name: 'Marta Colvin Andrate'},
  { id: 1, name: 'Lamidi Olonde Fakeye'},
  { id: 2, name: 'Louise Nevelson'},
]

export default function List() {
    const [artists, setArtists] = useState(initialArtists);
    return (
      <>
        {artists.map(artist => (
          <li key={artist.id}>
            {artist.name}{' '}
            <button onClick={() => {
              setArtists(
                artists.filter(a =>
                  a.id !== artist.id
                )
              );
            }}>
              删除
            </button>
          </li>
        ))}
      </>
    )
}

转换数组

想要改变数组中的某些或全部元素,可以使用map()创建一个新数组,传入map的函数决定
要根据每个元素的值或索引对元素做出处理

map()和filter()不会直接修改原始数组,reverse()和sort()会改变原始数组

状态管理

=> 数据 =》 组件之间流动

状态响应输入

使用React,无需直接从Code层面修改UI,eg: 不需要编写‘禁用按钮’、‘启用按钮’、‘显示成功消息’等,只需要描述组件在不同状态(初始状态、输入状态、成功状态),
根据用户输入触发状态更改。

import { useState } from 'react'

export default function Form() {
  const [anser, setAnswer] = useState('');
  const [error, setError] = useState(null);
  const [status, setStatus] = useState('typing');
  
  if(status === 'success') {
    return <h1>答对了!</h1>
  }

  async function handleSubmit(e) {
    e.preventDefault();
    setStatus('submitting');
    try {
      await submitForm(answer);
      setStatus('success');
    } catch (err) {
      setStatus('typing');
      setError(err);
    }
  }
  
  function handleTextareaChange(e) {
    setAnswer(e.target.value);
  }

  return (
    <>
      <form onSubmit={handleSubmit}>
        <textarea
          value={answer}
          onChange={handleTextareaChange}
          disabled={status === 'submitting'}
        />
        <button disabled={answer.length === 0 || status === 'submitting'}>
          提交
        </button>
        {
          error !== null && <p className='Error'> {error.message} </p>
        }
      </form>
    </>
  )
}

function submitForm(answer) {
  // 模拟接口请求
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      let shouldError = answer.toLowerCase() !== 'lima'
      if(shouldError) {
        reject(new Error('Right'));
      } else {
        resolve();
      }
    }, 1500);
  });
}

状态结构

// 状态不应该包含冗余或重复的信息
// 如果包含一些多余的状态会忘记去更新,从而导致问题产生

import { useState } from 'react';

export default function Form() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [fullName, setFullName] = useState('');

  function handleFirstNameChange(e) {
    setFirstName(e.target.value);
    setFullName(e.target.value + '' + lastName);
  }
}

在组件之间共享状态

希望两个组件的状态始终同步更改,将相关状态从两个组件上移除,
并将这些状态移动到最近的父级别组件,通过props将状态传递给两个组件
称之为“状态提升"

import { useState } from 'react'

export default function Accordion() {
  const [activeIndex, setActiveIndex] = useState(0);
  return (
    <>
      <Panel
        title='关于'
        isActive={activeIndex === 0}
        onShow={() => setActiveIndex(0)}/>
    </>
  )
}

提取状态逻辑到reducer中

将需要更新多个状态的组件来说,在组件外部将所有状态更新逻辑合并到一个成为“reducer”的函数
事件处理程序会变得很简洁,只需要指定用户的“actions”,在文件底部,reducer函数指定状态
应该如何更新以响应每个action

import { useReducer } from 'react'
import AddTask from './AddTask.js'
import TaskList from './TaskList.js'

export default function TaskApp() {
   const [tasks, dispatch] = useReducer(
      tasksReducer,
      initialTasks
   );

   function handleAddTask(text) {
      dispatch({
        type: 'added',
        id: nextId++,
        text: text,
      });
   }
    
   function handleChangeTask(task) {
      dispatch({
        type: 'changed',
        task: task,
      })
   }

   function handleDeleteTask(taskId) {
      dispatch({
          type: 'deleted',
          id: taskId,
      });
   }

   return (
      <>
          <AddTask onAddTask={handleAddTask}/>
          <TaskList
            tasks={tasks}
            onChangeTask={handleChangeTask}
            onDeleteTask={handleDeleteTask}/>
      </>
   )
}


function tasksReducer(tasks, action) {
    switch (action.type) {
        case 'added': {
            return [...tasks, {
                id: action.id,
                text: action.text,
                done: false,
            }]
        }
        case 'changed': {
            return tasks.map(t => {
                if(t.id === action.task.id) {
                    return action.task;
                } else {  
                    return t;
                }
            });
        }
        case 'deleted': {
             return tasks.filter(t => t.id !== action.id);
        }
        default: {
            throw Error('Unknown Operation: ' + action.type);
        }
    }
}


let nextId = 3;
const initialTasks = [
  { id: 0, text: '参观卡夫卡博物馆', done: true },
  { id: 1, text: '看木偶戏', done: false },
  { id: 2, text: '列侬墙图片', done: false }
];

使用Reducer和Context进行状态扩展

  • Reducer: 合并组件的状态更新逻辑
  • Context:将信息深入传递给其他组件
    利用Reducer和Context组合在一起实现对复杂应用状态的管理

使用reducer来管理一个具有复杂状态的父组件
组件树中任何深度的其他组件都可以通过context读取状态,
再通过dispatch来更新状态