React学习笔记

搭建一个react工程

react是一个渐进式框架,本次学习采用创建react工程的方法进行学习,本次学习使用Create React App脚手架进行项目的初始化

Create React App

创建项目

// my-app是项目名
npx create-react-app my-app

上面一步会直接下载好依赖,直接进目录运行就可以了。
然后就可以通过以下代码进入并运行项目了

cd my-app
npm start

创建组件

jsx语法

JSX 的语法比 HTML 更严格。类似
这样的标签是必须要关闭的。并且,组件也不能返回多个并列最高层级的 JSX 标签,你必须为所有最高层级的标签添加一个共同的父标签,例如使用

 <div>...</div> 或 <>...</> 作为父标签:

创建一个普通的组件

请注意, 标签以大写字母开头,这样就能便于识别这个是一个 React 组件。React 组件的名称必须始终以大写字母开头,而 HTML 标签必须全部为小写字母。
文件务必是jsx格式的。

// 创建一个按钮组件
export default function MyButton() {
  return (
    <button>Click me</button>
  );
}

创建一个MyApp组件测试,并引入MyButton组件

import MyButton from './MyBtn';
export default function MyApp() {
  return (
    <div>
      <h1>Welcome to my app</h1>
      <MyButton />
    </div>
  );
}

在 JSX 中使用大括号编写 JavaScript

jsx标签中,可以通过大括号来使用js定义好的变量,我们对MyApp进行改造,代码如下:

import MyButton from './MyBtn';
const hello = {
  text:"Welcome to my app2",
  theme:{
    color:"yellow"
  }
}
export default function MyApp() {
  return (
    <div>
      <h1 style={hello.theme}>{hello.text}</h1>
      <MyButton />
    </div>
  );
}

效果
在这里插入图片描述

组件间传参props

React 组件使用 props 来进行组件之间的通讯。每个父组件都可以通过为子组件提供 props 的方式来传递信息。props 可能会让你想起 HTML 属性,但你可以通过它们传递任何 JavaScript 的值,包括对象、数组、函数、甚至是 JSX!
修改代码通过MyApp给MyButton传参,
注意:MyButton接参数时需要通过对象接,具体的看代码

// MyApp
import MyButton from './MyBtn';
const hello = {
  text:"Welcome to my app2",
  theme:{
    color:"yellow"
  },
  btnText:"我是按钮"
}
export default function MyApp() {
  return (
    <div>
      <h1 style={hello.theme}>{hello.text}</h1>
      <MyButton text={hello.btnText} />
    </div>
  );
}
// MyButton
// 注意。这里接受参数用了{},如果有多个也得放大括号里比如{text,num}这样
export default function MyButton({text}) {
  return (
    <button>{text}</button>
  );
}

条件渲染

在 React 中,你可以使用 JavaScript 语法,如 if 语句、&& 和 ? : 操作符有条件地渲染 JSX。
就是jsx的if渲染方式,这里演示一个子组件通过props不通值来条件渲染,代码如下:

function Item({name,isPacked}) {
  return (
      <li>{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 中使用 JavaScript 的 filter() 和 map() 来实现数组的过滤和转换,将数据数组转换为组件数组。

对于数组的每个元素项,你需要指定一个 key。通常你需要使用数据库中的 ID 作为 key。即使列表发生了变化,React 也可以通过 key 来跟踪每个元素在列表中的位置。

修改刚才的列表组件,代码如下:
注意:一定要设置key

function Item({name,isPacked}) {
  return (
      <li>{name}{isPacked && "√"}</li>
  );
}
const arrObj = [
  {name:"Space suit",isPacked:true},
  {name:"Helmet with a golden leaf",isPacked:true},
  {name:"Photo of Tam",isPacked:false},
]
export default function PackingList() {
  // 修改为循环的
  const listItem = arrObj.map((i)=>{
    return (<Item
      isPacked={i.isPacked}
      name={i.name}
      key={i.name}
    />)
    
  })
  return (
    <section>
      <h1>Sally Ride's Packing List</h1>
      <ul>
       {listItem}
      </ul>
    </section>
  );
}

添加交互

相应事件

React 允许你向 JSX 中添加事件处理程序。事件处理程序是你自己的函数,它将在用户交互时被触发,如点击、悬停、焦点在表单输入框上等等。

等内置组件只支持内置浏览器事件,如 onClick。但是,你也可以创建你自己的组件,并给它们的事件处理程序 props 指定你喜欢的任何特定于应用的名称。
以下代码定义了onclick触发的逻辑,并且运用了插槽

export default function Toolbar({ onPlayMovie, onUploadImage }) {
  return (
    <div>
      <Button onClick={onPlayMovie}>
        Play Movie
      </Button>
      <Button onClick={onUploadImage}>
        Upload Image
      </Button>
    </div>
  );
}

function Button({ onClick, children }) {
  return (
    <button onClick={onClick}>
      {children}
    </button>
  );
}
// app里使用
import MyButton from './MyBtn';
import PackingList from './PackingList';
import MyToolbar from './MyToolbar';
const hello = {
  text:"Welcome to my app2",
  theme:{
    color:"yellow"
  },
  btnText:"我是按钮"
}
export default function MyApp() {
  return (
    <div>
      <h1 style={hello.theme}>{hello.text}</h1>
      <MyButton text={hello.btnText} />
      <PackingList></PackingList>
      
      <MyToolbar onPlayMovie={()=>{alert('Playing!')}} onUploadImage={() => alert('Uploading!')}></MyToolbar>
      
    </div>
  );
}

State: 组件的记忆

你可以用 useState Hook 为组件添加状态。Hook 是能让你的组件使用 React 功能的特殊函数(状态是这些功能之一)。useState Hook 让你声明一个状态变量。它接收初始状态并返回一对值:当前状态,以及一个让你更新状态的设置函数。
注意:

  • 使用useState Hook时,必须必须将其包在方法中,不能直接写在块里
  • const[index,setIndex]=useState(0)返回两个值index是值,setIndex相当于set函数,通过它给setIndex赋值
  • index值发生变化时视图刷新。
  • 补充,其实调用setIndex方法时,是去请求react按照新值重新渲染页面。但不会在已运行的代码中更改它,也就是在重新渲染前index不变,setIndex(index+1),不管连续调用多少次都是0+1 为1。
    代码如下:
import {useState} from 'react';
const list = [{name:"Homenaje a la Neurocirugía"},{name:"Floralis Genérica"},{name:"Eternal Presence"}]
// 注意,使用useState是外层得包一个方法
export default function Gallery(){
  // 返回的第一个参数index是目前的值,这里就是0,
  // 返回的第二个参数setIndex,可以通过它来修改index的值,比如setIndex(5)后index就变成5了
  // 具体应用可以参考下面的handleNextClick方法和handleMoreClick方法
  const [index,setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false)
  // 数组切换的逻辑
  const hasNext = index < list.length - 1
  function handleNextClick() {
    if (hasNext) {
      setIndex(index + 1);
    } else {
      setIndex(0);
    }
  }
  function handleMoreClick() {
    setShowMore(!showMore);
  }
  let sculpture = list[index]
  return (
    <>
      <button onClick={handleNextClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i>
      </h2>
      <h3>
        ({index + 1} of {list.length})
      </h3>
      {/* 这是true和false变化的逻辑部分 */}
      <button onClick={handleMoreClick}>
        {showMore ? 'Hide' : 'Show'} details
      </button>
    </>
  )
}

作为快照的状态

与普通 JavaScript 变量不同,React 状态的行为更像一个快照。设置它并不改变你已有的状态变量,而是触发一次重新渲染。这在一开始可能会让人感到惊讶!

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

React 这样工作是为了帮助你避免微妙的 bug。这里有一个小的聊天应用程序。试着猜一猜,如果先按下“发送”,然后再把收件人改为 Bob,会发生什么?五秒钟后,谁的名字会出现在 alert 中?

// 测试快照的问题
import { useState } from 'react';

export default function Form() {
  // 每次值变化时,触发的时重新渲染,
  // 推测在渲染时重新调用了Form方法。所以之前调用的handlerSumit的定时器内的变量值不会变。
  // 你可以在发送后改变页面中的内容来看到这个现象。
  const [to, setTo] = useState('Alice');
  const [message, setMessage] = useState('Hello');
  
  function handleSubmit(e) {
    e.preventDefault();
    setTimeout(() => {
      alert(`You said ${message} to ${to}`);
    }, 5000);
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        To:{' '}
        <select
          value={to}
          onChange={e => setTo(e.target.value)}>
          <option value="Alice">Alice</option>
          <option value="Bob">Bob</option>
        </select>
      </label>
      <textarea
        placeholder="Message"
        value={message}
        onChange={e => setMessage(e.target.value)}
      />
      <button type="submit">Send</button>
    </form>
  );
}

更新状态中的对象

状态可以持有任何类型的 JavaScript 值,包括对象。但你不应该直接改变你在 React 状态中持有的对象和数组。相反,当你想更新一个对象和数组时,你需要创建一个新的对象(或复制现有的对象),然后用这个副本来更新状态。

通常情况下,你会使用 … 展开语法来复制你想改变的对象和数组。例如,更新一个嵌套对象可以是这样的:

import { useState } from 'react';

export default function Form() {
  // 状态管理中的数据可以是对象,也可以是数组
  // 当是对象时,状态发生变化时,不要直接在原对象上修改,因为react要保留每次变更的数据的
  // 所以需要我们需要复制该对象,通过传入新的对象来修改状态。
  const [person, setPerson] = useState({
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
    }
  });

  function handleNameChange(e) {
    setPerson({
      ...person,
      name: e.target.value
    });
  }

  function handleTitleChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        title: e.target.value
      }
    });
  }

  function handleCityChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        city: 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>
      <p>
        <i>{person.artwork.title}</i>
        {' by '}
        {person.name}
        <br />
        (located in {person.artwork.city})
      </p>
    </>
  );
}

更新状态中的数组

数组是另一种可以存在状态中的可变 JavaScript 对象,应将其视为只读。就像对象一样,当你想更新存在状态中的数组时,你需要创建一个新数组(或者复制现有数组),然后用新数组来更新状态。

import { useState } from 'react';

let nextId = 3;
const initialList = [
  { id: 0, title: 'Big Bellies', seen: false },
  { id: 1, title: 'Lunar Landscape', seen: false },
  { id: 2, title: 'Terracotta Army', seen: true },
];

export default function BucketList() {
  const [list, setList] = useState(
    initialList
  );

  function handleToggle(artworkId, nextSeen) {
    setList(list.map(artwork => {
      if (artwork.id === artworkId) {
        return { ...artwork, seen: nextSeen };
      } else {
        return artwork;
      }
    }));
  }

  return (
    <>
      <h1>Art Bucket List</h1>
      <h2>My list of art to see:</h2>
      <ItemList
        artworks={list}
        onToggle={handleToggle} />
    </>
  );
}

function ItemList({ artworks, onToggle }) {
  return (
    <ul>
      {artworks.map(artwork => (
        <li key={artwork.id}>
          <label>
            <input
              type="checkbox"
              checked={artwork.seen}
              onChange={e => {
                onToggle(
                  artwork.id,
                  e.target.checked
                );
              }}
            />
            {artwork.title}
          </label>
        </li>
      ))}
    </ul>
  );
}

状态管理

随着你的应用不断变大,更有意识的去关注应用状态如何组织,以及数据如何在组件之间流动会对你很有帮助。冗余或重复的状态往往是缺陷的根源。在本节中,你将学习如何组织好状态,如何保持状态更新逻辑的可维护性,以及如何跨组件共享状态。

使用状态响应输入

与vue一样,利用状态判断按钮禁用,显示哪些组件等,只不过可以在方法中直接return不同的jsx来更明显的修改。

选择状态结构

useState变量不要太冗余,比如全名和firstName的显示

import { useState } from 'react';

export default function Form() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  // 可以理解成计算属性?
  const fullName = firstName + ' ' + lastName;

  function handleFirstNameChange(e) {
    setFirstName(e.target.value);
  }

  function handleLastNameChange(e) {
    setLastName(e.target.value);
  }

  return (
    <>
      <h2>让我们帮你登记</h2>
      <label>
        名:{' '}
        <input
          value={firstName}
          onChange={handleFirstNameChange}
        />
      </label>
      <label>
        姓:{' '}
        <input
          value={lastName}
          onChange={handleLastNameChange}
        />
      </label>
      <p>
        你的票将发给:<b>{fullName}</b>
      </p>
    </>
  );
}

在组件间共享状态

有时候你希望两个组件的状态始终同步更改。要实现这一点,可以将相关状态从这两个组件上移除,并把这些状态移到最近的父级组件,然后通过 props 将状态传递给这两个组件。这被称为“状态提升”,这是编写 React 代码时常做的事。

在以下示例中,要求每次只能激活一个面板。要实现这一点,父组件将管理激活状态并为其子组件指定 prop,而不是将激活状态保留在各自的子组件中。

保留和重置状态

通过设置key ,当key变化时对应组件数据就算没变化也会强制重新渲染。和vue的key的用法类似

提取状态逻辑到 reducer 中

对于那些需要更新多个状态的组件来说,过于分散的事件处理程序可能会令人不知所措。对于这种情况,你可以在组件外部将所有状态更新逻辑合并到一个称为 “reducer” 的函数中。这样,事件处理程序就会变得简洁,因为它们只需要指定用户的 “actions”。在文件的底部,reducer 函数指定状态应该如何更新以响应每个 action!

// 将state逻辑抽出去的一个尝试,
// 注意,这里引入的就是useReducer了。
import { useReducer } from 'react';


export default function TaskApp() {
  // initialTasks是数据源
  // tasksReducer是处理数据源的某个方法,里面可以包含不同的处理数据源的逻辑。
  // tasksReducer方法接受两个参数,第一个是useReducer绑定的数据源,这里就是tasks,第二个参数是dispath传入的内容。
  // 我们来看useReducer得到的两个变量
  // tasks是initialTasks得到的响应式数据源
  // dispatch是修改数据源的方法,这里绑定了tasksReducer。那么dispatch传入的参数作为tasksReducer方法第二个参数被传入。且调用dispatch时触发一次数据变动,重新渲染
  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
    });
  }
  const listDiv = tasks.map((item,index)=>{
    return (
    <div key={item.id}>
      <span>{item.text}</span>
      <input type="checkbox" checked={item.done} onClick={()=>{handleChangeTask({...item,done:!item.done})}}/>
      <button onClick={()=>{handleDeleteTask(index)}}>删除</button>
    </div>
    )
  })

  return (
    <>
      <h1>布拉格行程</h1>
      {listDiv}
    </>
  );
}
// 作为useReducer修改数据的方法和useState的注意事项一样,如果是复杂数据类型,搞一份深拷贝的数据再修改后直接返回。
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('未知操作:' + action.type);
    }
  }
}

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值