一小时学会React基础

一小时学会React基础

作者:markzzw(zhangzewei) 日期:2024年1月15日
代码样例:Codesandbox
原文链接

读者将会通过本篇文章对React的基础有一定的认识,能够使用其基本的特性和功能进行小规模项目或者组件的开发。

本文所教学的React版本为React 18,将使用 react 进行一个简单的todolist的编写。

工具

对于程序员来说,写代码的工具是必不可少的,这里我推荐的是微软出品的vscode,这个是前端主流的开发工具,当然也可以使用其他的工具例如:vim,sublime,atom,webstorm等。

以上工具都是需要在电脑中安装了才可以使用的,对于快速学习而言,或者对于没有在编程环境的情况下,我们可以使用一些线上的工具进行学习,本文将会使用 CodeSandbox 进行教学。

首先需要注册一个 CodeSandbox 的账号,推荐使用 github 账号进行关联注册,然后点击右上角 创建按钮(create),选择 Create a Sandbox 在弹出层的搜索框内输入关键词 react,随后选择 React,网站就能帮你自动创建一个 react 的项目。

React 的函数组件

在创建好的项目中,打开 App.jsx / App.js 后可以看到,里面的主体是一个函数,这个就是 react 的函数式组件,我们可以尝试修改其中标签内部的文字,然后保存文件,就能在右边的视窗中查看到更新的内容。

jsx 复制代码
export default function App() {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

JSX语法

在文件中,React 的函数组件返回了一个html的结构,这个结构是react的 jsx 语法,react 能够将其转变成为包含嵌套关系的 react 自己的创建节点的函数,在初学的过程中我们只需要理解到html结构就是这个 react 组件的 html 结构,能够展示在页面上的结构即可。

那么接下来,我们将使用 jsx 语法在 App 函数中书写完成一个 todolist 的基础 html 结构,但是注意在jsx的语法中,样式标签class 变成了 className

jsx 复制代码
// 样式引入
import './styles.css';

export default function App() {
  return (
    <div className="container">
      <div className="row">
        <div className="page-header">
          <h1>TodoList</h1>
        </div>

        <div className="input-group">
            <input
            type="text"
            className="form-control"
            placeholder="Add todo ..."
            />
            <span className="input-group-btn">
            <button className="btn btn-default" type="button">
                Go!
            </button>
            </span>
        </div>
        <ul className="list-group">
            <li className="list-group-item list-item">
                <span>todo</span>
                <div className="btn-group" role="group">
                    <button
                    type="button"
                    className="btn btn-danger"
                    >
                        Delete
                    </button>
                    <button
                    type="button"
                    className="btn btn-primary"
                    >
                        Done
                    </button>
                </div>
            </li>
        </ul>
      </div>
    </div>
  );
}

保存刷新页面之后得到下面界面。

这个界面比较粗糙,我们使用bootstrap为其添加一些样式,先打开Bootstrap3

拷贝以下代码粘贴进入 public/index.htmlhead 部分

html 复制代码
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">

然后在 src/styles.css 文件中添加

css 复制代码
.list-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: large;
}

就能够得到一个较为整洁的界面

useState 的使用

在react的框架体系中,我们需要知道一件事,就是数据驱动界面,也就是通过改变数据进而改变我们的界面,react 中最小的数据管理方案就是使用useState 管理当前组件的 state。

App.js 中添加

jsx 复制代码
// 引入 useState
import { useState } from 'react'
// 样式引入
import './styles.css';

export default function App() {
    const [todolist, setTodolist] = useState([]);
    return (
        <div className="container">
            <div className="row">
                <div className="page-header">
                <h1>TodoList</h1>
                </div>

                <div className="input-group">
                    <input
                    type="text"
                    className="form-control"
                    placeholder="Add todo ..."
                    />
                    <span className="input-group-btn">
                    <button className="btn btn-default" type="button">
                        Go!
                    </button>
                    </span>
                </div>
                <ul className="list-group">
                    { // 使用 {} 能够在jsx中书写js表达式
                        todolist.map((item, index) => (
                            // key 是返回数组html的必须参数,它能够帮助react进行数组html的更新,key必须是唯一的
                            <li className="list-group-item list-item" key={index}>
                                <span>todo</span>
                                <div className="btn-group" role="group">
                                    <button
                                    type="button"
                                    className="btn btn-danger"
                                    >
                                        Delete
                                    </button>
                                    <button
                                    type="button"
                                    className="btn btn-primary"
                                    >
                                        Done
                                    </button>
                                </div>
                            </li>
                        ))
                    }
                </ul>
            </div>
        </div>
    );
}

随后我们可以将

jsx 复制代码
const [todolist, setTodolist] = useState([]);

改为

jsx 复制代码
const [todolist, setTodolist] = useState([{
    text: 'todo',
    status: 'active'
}]);

我们会发现页面上原本没有todolist的,但是现在出现了第一条,首先useState()函数可以传入初始值,然后返回一个数组,数组的第一个是当前的 state,第二个是更新 state 的函数,利用 useState(),我们可以实现一个简单的todolist。

函数绑定

现在我们可以进一步,新增添加一个todo的功能,我们期望在输入框中输入,然后点击 Go! 按钮进行添加,这里就需要对 html 元素进行函数的绑定,我们知道在一般对元素绑定函数的方法为在html上写出on-[操作名称]的方式(参考链接),例如:<input onchange="myFunction()">,那么在jsx中我们依旧采用这种方法,不一样的点是jsx中采用驼峰命名方法,然后使用华括弧包裹住函数,例如:<input onChange={myFunction}>

jsx 复制代码
import "./styles.css";
import { useState } from "react";

export default function App() {
  const [todolist, setTodolist] = useState([
    {
      text: "todo",
      status: "active",
    },
  ]);
  // 控制输入框的值
  const [inputValue, setInputValue] = useState("");
  // 监听输入框的值修改
  const handleInput = (e) => {
    const value = e.target.value.trim();
    setInputValue(value);
  };
  // 添加一条todo
  const handleAdd = () => {
    if (inputValue) {
      // 每次setstate都需要是一个全新的值
      setTodolist(
        todolist.concat({
          text: inputValue,
          status: "active",
        })
      );
      setInputValue("");
    }
  };
  return (
    <div className="container">
      <div className="row">
        <div className="page-header">
          <h1>TodoList</h1>
        </div>

        <div className="input-group">
          <input
            type="text"
            className="form-control"
            placeholder="Add todo ..."
            value={inputValue}
            // 函数绑定
            onChange={handleInput}
          />
          <span className="input-group-btn">
            <button
              // 函数绑定
              onClick={handleAdd}
              className="btn btn-default"
              type="button"
            >
              Go!
            </button>
          </span>
        </div>
        <ul className="list-group">
          {
            // 使用 {} 能够在jsx中书写js表达式
            todolist.map((item, index) => (
              // key 是返回数组html的必须参数,它能够帮助react进行数组html的更新,key必须是唯一的
              <li className="list-group-item list-item" key={index}>
                
                <span>{
                    // 将获取到的todo对象的文字展示出来 
                    item.text
                }</span>
                <div className="btn-group" role="group">
                  <button type="button" className="btn btn-danger">
                    Delete
                  </button>
                  <button type="button" className="btn btn-primary">
                    Done
                  </button>
                </div>
              </li>
            ))
          }
        </ul>
      </div>
    </div>
  );
}

在使用 input 和其值的时候,react 建议我们使用 state 进行 input 的 value 的控制,这样子符合 react 的数据变更引起界面变更的原则。

那么接下来一样的使用 useState() 完成 完成todo/删除todo 的功能。

jsx 复制代码
import "./styles.css";
import { useState } from "react";

export default function App() {
  const [todolist, setTodolist] = useState([
    {
      text: "todo",
      status: "active",
    },
  ]);
  const [inputValue, setInputValue] = useState("");
  const handleInput = (e) => {
    const value = e.target.value.trim();
    setInputValue(value);
  };

  const handleAdd = () => {
    if (inputValue) {
      setTodolist(
        todolist.concat({
          text: inputValue,
          status: "active",
        })
      );
      setInputValue("");
    }
  };

  const handleDelete = (index) => {
    // 在todolist数组中筛选出不是当前项的items然后重新setState达到delete的效果
    const newList = todolist.filter((_, idx) => index !== idx);
    setTodolist(newList);
  };

  const handleDone = (item, idx) => {
    // 根据 status 对当前item进行数据更新然后返回新的todolist数组进行setState
    if (item.status === "active") {
      const newList = todolist.map((item, index) => ({
        ...item,
        status: idx === index ? "done" : item.status,
      }));
      setTodolist(newList);
    } else {
      const newList = todolist.map((item, index) => ({
        ...item,
        status: idx === index ? "active" : item.status,
      }));
      setTodolist(newList);
    }
  };
  return (
    <div className="container">
      <div className="row">
        <div className="page-header">
          <h1>TodoList</h1>
        </div>

        <div className="input-group">
          <input
            type="text"
            className="form-control"
            placeholder="Add todo ..."
            // 绑定state上的value
            value={inputValue}
            // 绑定函数
            onChange={handleInput}
          />
          <span className="input-group-btn">
            <button
              // 绑定函数
              onClick={handleAdd}
              className="btn btn-default"
              type="button"
            >
              Go!
            </button>
          </span>
        </div>
        <ul className="list-group">
          {
            // 使用 {} 能够在jsx中书写js表达式,通过map返回html数组
            todolist.map((item, index) => (
              // key 是返回数组html的必须参数,它能够帮助react进行数组html的更新,key必须是唯一的
              <li className="list-group-item list-item" key={index}>
                <span
                  className={
                    // 使用status对其样式进行不一样的渲染
                    item.status
                  }
                >
                  {item.text}
                </span>
                <div className="btn-group" role="group">
                  <button
                    onClick={() => handleDelete(index)}
                    type="button"
                    className="btn btn-danger"
                  >
                    Delete
                  </button>
                  <button
                    onClick={() => handleDone(item, index)}
                    type="button"
                    className="btn btn-primary"
                  >
                    {
                      // 通过status进行文案的重新渲染
                      item.status === "active" ? "Done" : "Undone"
                    }
                  </button>
                </div>
              </li>
            ))
          }
        </ul>
      </div>
    </div>
  );
}

组件抽离

随着开发的继续,代码会逐渐增多,如果将代码都放在一个文件中会变得难以维护,所以我们会对组件进行抽离,简单组件抽离就是将单个的功能的代码放在一个文件里面,然后导出给其他文件使用,那么我们将进行如下简单抽离,抽离出来的组件依然是一个函数的形式。

AddTodo.js

jsx 复制代码
import { useState } from "react";
export default function AddTodo() {
  const [inputValue, setInputValue] = useState("");
  const handleInput = (e) => {
    const value = e.target.value.trim();
    setInputValue(value);
  };

  const handleAdd = () => {
    if (inputValue) {
      setTodolist(
        todolist.concat({
          text: inputValue,
          status: "active",
        })
      );
      setInputValue("");
    }
  };
  return (
    <div className="input-group">
      <input
        type="text"
        className="form-control"
        placeholder="Add todo ..."
        // 绑定state上的value
        value={inputValue}
        // 绑定函数
        onChange={handleInput}
      />
      <span className="input-group-btn">
        <button
          // 绑定函数
          onClick={handleAdd}
          className="btn btn-default"
          type="button"
        >
          Go!
        </button>
      </span>
    </div>
  );
}

Todos.js

jsx 复制代码
import { useState } from "react";
export default function Todos() {
  const [todolist, setTodolist] = useState([
    {
      text: "todo",
      status: "active",
    },
  ]);
  const handleDelete = (index) => {
    // 在todolist数组中筛选出不是当前项的items然后重新setState达到delete的效果
    const newList = todolist.filter((_, idx) => index !== idx);
    setTodolist(newList);
  };

  const handleDone = (item, idx) => {
    // 根据 status 对当前item进行数据更新然后返回新的todolist数组进行setState
    if (item.status === "active") {
      const newList = todolist.map((item, index) => ({
        ...item,
        status: idx === index ? "done" : item.status,
      }));
      setTodolist(newList);
    } else {
      const newList = todolist.map((item, index) => ({
        ...item,
        status: idx === index ? "active" : item.status,
      }));
      setTodolist(newList);
    }
  };
  return (
    <ul className="list-group">
      {
        // 使用 {} 能够在jsx中书写js表达式,通过map返回html数组
        todolist.map((item, index) => (
          // key 是返回数组html的必须参数,它能够帮助react进行数组html的更新,key必须是唯一的
          <li className="list-group-item list-item" key={index}>
            <span
              className={
                // 使用status对其样式进行不一样的渲染
                item.status
              }
            >
              {item.text}
            </span>
            <div className="btn-group" role="group">
              <button
                onClick={() => handleDelete(index)}
                type="button"
                className="btn btn-danger"
              >
                Delete
              </button>
              <button
                onClick={() => handleDone(item, index)}
                type="button"
                className="btn btn-primary"
              >
                {
                  // 通过status进行文案的重新渲染
                  item.status === "active" ? "Done" : "Undone"
                }
              </button>
            </div>
          </li>
        ))
      }
    </ul>
  );
}

App.js

jsx 复制代码
import "./styles.css";
import { useState } from "react";
import AddTodo from "./AddTodo";
import Todos from "./Todos";

export default function App() {
  return (
    <div className="container">
      <div className="row">
        <div className="page-header">
          <h1>TodoList</h1>
        </div>
        <AddTodo />
        <Todos />
      </div>
    </div>
  );
}

Props 的使用

但是我们发现现在点击 Go! 按钮会报错,这是因为数据被分开了,不能够对todolist进行增加操作了,要解决这个问题就需要让 <AddTodo /><Todos /> 共享数据,这时候就需要使用到 props 把数据传入各个组件内部进行渲染。

Props 就是该组件的参数,绑定props对象的方法就是直接在组件外部的jsx上写上其props的名称,例如:

jsx 复制代码
function Todos(props) {
    // 获取props
    const {
        todos,
        onDone,
        onDelete
    } = props;
}

// 设置props
<Todos
    todos={todolist}
    onDelete={handleDelete}
    onDone={handleDone}
/>

首先还是将 todolist 放在 App.js 这一层组件中,<AddTodo /><Todos /> 可以提供函数操作的 props 对todolist进行操作,进而达到更新的目的,然后重新渲染。

AddTodo.js

jsx 复制代码
import { useState } from "react";
export default function AddTodo(props) {
  // 获取props
  const { onAdd } = props;
  const [inputValue, setInputValue] = useState("");
  const handleInput = (e) => {
    const value = e.target.value.trim();
    setInputValue(value);
  };

  const handleAdd = () => {
    if (inputValue) {
      onAdd(inputValue);
      setInputValue("");
    }
  };
  return (
    <div className="input-group">
      <input
        type="text"
        className="form-control"
        placeholder="Add todo ..."
        // 绑定state上的value
        value={inputValue}
        // 绑定函数
        onChange={handleInput}
      />
      <span className="input-group-btn">
        <button
          // 绑定函数
          onClick={handleAdd}
          className="btn btn-default"
          type="button"
        >
          Go!
        </button>
      </span>
    </div>
  );
}

Todos.js

jsx 复制代码
export default function Todos(props) {
  // 获取props
  const { todos, onDone, onDelete } = props;
  const handleDelete = (index) => {
    // 在todos数组中筛选出不是当前项的items然后重新setState达到delete的效果
    const newList = todos.filter((_, idx) => index !== idx);
    onDelete && onDelete(newList);
  };

  const handleDone = (item, idx) => {
    // 根据 status 对当前item进行数据更新然后返回新的todos数组进行setState
    if (item.status === "active") {
      const newList = todos.map((item, index) => ({
        ...item,
        status: idx === index ? "done" : item.status,
      }));
      onDone && onDone(newList);
    } else {
      const newList = todos.map((item, index) => ({
        ...item,
        status: idx === index ? "active" : item.status,
      }));
      onDone && onDone(newList);
    }
  };
  return (
    <ul className="list-group">
      {
        // 使用 {} 能够在jsx中书写js表达式,通过map返回html数组
        todos.map((item, index) => (
          // key 是返回数组html的必须参数,它能够帮助react进行数组html的更新,key必须是唯一的
          <li className="list-group-item list-item" key={index}>
            <span
              className={
                // 使用status对其样式进行不一样的渲染
                item.status
              }
            >
              {item.text}
            </span>
            <div className="btn-group" role="group">
              <button
                onClick={() => handleDelete(index)}
                type="button"
                className="btn btn-danger"
              >
                Delete
              </button>
              <button
                onClick={() => handleDone(item, index)}
                type="button"
                className="btn btn-primary"
              >
                {
                  // 通过status进行文案的重新渲染
                  item.status === "active" ? "Done" : "Undone"
                }
              </button>
            </div>
          </li>
        ))
      }
    </ul>
  );
}

App.js

jsx 复制代码
import "./styles.css";
import { useState } from "react";
import AddTodo from "./AddTodo";
import Todos from "./Todos";

export default function App() {
  const [todolist, setTodolist] = useState([
    {
      text: "todo",
      status: "active",
    },
  ]);
  const handleAdd = (value) =>
    setTodolist(
      todolist.concat({
        text: value,
        status: "active",
      })
    );
  const handleDelete = (newList) => {
    setTodolist(newList);
  };
  const handleDone = (newList) => {
    setTodolist(newList);
  };
  return (
    <div className="container">
      <div className="row">
        <div className="page-header">
          <h1>TodoList</h1>
        </div>
        <AddTodo onAdd={handleAdd} />
        <Todos todos={todolist} onDelete={handleDelete} onDone={handleDone} />
      </div>
    </div>
  );
}

useEffect 的简单使用

目前来说我们这个todolist是一个一次性的页面,当我们刷新之后,之前的todolist全部会被清空,在一般的开发过程中,我们会将todolist保存在某个数据库中,然后在界面加载的时候去将其取回,这里需要用 useEffect(() => {}, []) 去做存取的操作。

UseEffect 的第二个参数是一个依赖数组,如果数组为空,则在组件渲染初期运行一次第一个参数函数,如果数组内部有值,则当里面的某一个值发生变化就执行一次第一个参数函数。

在这里我们将todolist存在localstorage,然后在第一次加载的时候去获取历史记录,在todolist发生改变的时候存储当前todolist。

在这里在 setState() 中写一个回调函数,这个回调函数的参数为未更新的state,然后可以在函数内部对state做一些操作之后将其返回,返回的这个值就会更新成为新的state。

App.js

jsx 复制代码
import "./styles.css";
import { useEffect, useState } from "react";
import AddTodo from "./AddTodo";
import Todos from "./Todos";

export default function App() {
  const [todolist, setTodolist] = useState([]);
  const getTodoFromLocalstorage = () => {
    const todos = localStorage.getItem("todos");
    if (todos) return JSON.parse(todos);
    return [];
  };
  useEffect(() => {
    // 从数据存储源获取数据,一般这里会从后端获取数据
    setTodolist(getTodoFromLocalstorage);
  }, []);
  const handleAdd = (value) =>
    setTodolist((prevList) => {
      const newList = prevList.concat({
        text: value,
        status: "active",
      });
      localStorage.setItem("todos", JSON.stringify(newList));
      return newList;
    });
  const handleTodoChange = (newList) => {
    setTodolist(() => {
      localStorage.setItem("todos", JSON.stringify(newList));
      return newList;
    });
  };
  return (
    <div className="container">
      <div className="row">
        <div className="page-header">
          <h1>TodoList</h1>
        </div>
        <AddTodo onAdd={handleAdd} />
        {todolist && <Todos todos={todolist} onChange={handleTodoChange} />}
      </div>
    </div>
  );
}

结束

至此,以上就是react的基础知识和操作,记住两个原则

  1. 数据更改决定ui界面更改
  2. 函数式编程,传入参数变化函数返回结果随之变化 引申出,数据变化,导致ui界面重新渲染。
相关推荐
Ticnix15 分钟前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人18 分钟前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl22 分钟前
OpenClaw 深度技术解析
前端
崔庆才丨静觅25 分钟前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人34 分钟前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼37 分钟前
shadcn/ui,给你一个真正可控的UI组件库
前端
布列瑟农的星空41 分钟前
前端都能看懂的Rust入门教程(三)——控制流语句
前端·后端·rust
Mr Xu_1 小时前
Vue 3 中计算属性的最佳实践:提升可读性、可维护性与性能
前端·javascript
jerrywus1 小时前
我写了个 Claude Code Skill,再也不用手动切图传 COS 了
前端·agent·claude
玖月晴空1 小时前
探索关于Spec 和Skills 的一些实战运用-Kiro篇
前端·aigc·代码规范