React快速入门(核心概念+实战篇)

React快速入门

全部代码:https://github.com/ziyifast/front-demo

  • React特点:
  1. 声明式设计:声明范式
  2. 高效:使用VDOM,减少DOM的交互
  3. 灵活:与已知的库或框架完好配合
  4. JSX:一种独立的语言,试图解决很多JS的缺陷,ES6包含了几乎所有JSX的特性
  5. 组件:代码复用
  6. 单向响应数据流:比双向绑定更简单,更快。

1 核心篇

准备:创建项目

我这里使用VSCode创建项目,在终端执行下面命令

bash 复制代码
# 通过脚手架创建项目
npx create-react-app 1-react-core-demo
# npx create-react-app@latest # 创建项目,自选功能

# 进入项目
cd 1-react-core-demo

# 启动项目
npm start

App.js为React默认主页面

1.1 核心概念及语法

①组件:函数式组件

React分为函数式组件和类组件,官方推荐使用函数式组件,因为类组件编写起来较复杂。我这里主要演示类组件用法。

App.js:

解释:我下面定义了一个函数式组件App,并且将其return暴露

  • 注意点:
  • React使用语法为JSX(JS+HTML),只能有一个根标签=》<></>或者Fragment。所以我在多个<div>标签外用<>标签包裹了一层
javascript 复制代码
import './App.css';

function App() {
  return (
    // JSX(JavaScript+HTML)规定只能有一个根标签,如果需要添加标签
    // 1. 可以在外面嵌套一层<div>
    // 2. 使用<></>空标签
    // 3. 如果有id,可以使用Fragment
    <>
    <div className="App">
      <div>let's do it</div>
    </div>
    <div>who want to try ?</div>
    </>
  );
}

export default App;
②插值

解释:类似引用,我先定义一个值,然后通过{变量名}引用值

App.js

javascript 复制代码
function App() {
  // 插值(先定义变量,然后通过花括号引用值):{} 
  let content = "hello world"
  return (
      <div>{content}</div>
  );
}

export default App;
③数据渲染

数据渲染主要有:

  1. 条件渲染(if)
  2. 列表渲染(array)=> map遍历
javascript 复制代码
import './App.css';

function App() {
  // 数据渲染:
  // 1. 条件渲染
  // 2. 列表渲染

  //--------------1. 条件渲染-------------
  // let content = "hello world"
  // let flag = false
  // if (flag) {
  //   //注意:此处不用加引号
  //   content = <div>老侄,flag为true</div>
  // } else {
  //   content = <div>大舅,flag为false</div>
  // }
  // return (
  //     <div className="App">
  //       <span>嘿 man</span>
  //       {content}
  //     </div>
  // );

  //--------------2. 列表渲染-------------
  let userList = [
    {id:1,name:"徐杰"},
    {id:2,name:"郭艾伦"},
    {id:3,name:"易建联"}
  ]
  // 通过map来遍历
  const listContent = userList.map((item,index)=>{
    return <li key={index}>{item.name}</li>
  })
  return (
    <div className="App">
      <div>{listContent}</div>
    </div>
  );
}
export default App;
④事件处理
  1. 定义function
  2. 通过{funciton_name}引用
  3. 标签内绑定事件,onClick等
javascript 复制代码
import './App.css';

function App() {
  // 事件处理:无法实现响应式效果,需要结合useState来实现
  function handleClick(e){
    console.log("浏览器传入的值:"+e.target.innerText);
  }
  return (
    <div className="App">
      <button onClick={handleClick}>Click me</button>
    </div>
  );
}
export default App;
⑤状态:useState

通过useState实现响应式效果

  • const [content, setContent] = useState("Hello World")
  • useState会返回一个数组,第一个值为保存的值,第二个值是一个func,用于设置新的值
javascript 复制代码
import './App.css';
import {useState} from 'react'; //导入钩子函数

function App() {
  // 状态处理:useState实现,本质是将之前的值全部替换,因此setContent需要带上之前的值
  // 1. 字符串
  // 2. 对象
  // 3. 数组
  //------------------1. 字符串--------------------
  // const [content, setContent] = useState("Hello World")
  // function handleClick(){
  //   setContent("中国🇨🇳男篮")
  // }
  // return (
  //   <div className="App">
  //     <div>{content}</div>
  //     <button onClick={handleClick}>Click me</button>
  //   </div>
  // );

  //------------------2. 对象--------------------
  // const [content, setContent] = useState({
  //   name:"Jackson",
  //   age:20
  // })
  // function handleClick(){
  //   setContent({
  //     ...content, //...content是将对象之前的内容全部拿过来(useState的set操作是全部替换,如果这里只赋值age,那么会丢失name)
  //     age:18 //如果之前了age字段,那么我们这里再赋值就会替换之前的值
  //   })
  // }
  // return (
  //   <div>
  //     <div>{content.name}</div>
  //     <div>{content.age}</div>
  //     <button onClick={handleClick}>点我变小</button>
  //   </div>
  // )

  //------------------3. 数组--------------------
  const [content, setContent] = useState([
    {id:1, name:"库里"},
    {id:2, name:"欧文"},
    {id:3, name:"姚明"},
  ])
  const listData = content.map(item => (
    <li key={item.id}>{item.id}-{item.name}</li>
  ))
  //点击之后在默认添加元素
  function handleClick(){
    setContent([
      ...content,
      {id:4, name:"詹姆斯"}
    ])
  }
  //点击之后通过filter删除元素
  function handleClick2(){
    setContent(content.filter(item => item.id !== 2))
  }

  return (
    <div>
        <ul>{listData}</ul>
        <div>what?</div>
        <button onClick={handleClick}>点我新增</button>
        <button onClick={handleClick2}>点我删除2号</button>
    </div>
  )
}
export default App;

1.2 组件通信与插槽

组件:

  • React DOM组件
  • React 自定义组件
①React DOM组件:如img标签
javascript 复制代码
import './App.css';
import image from './logo.svg'

function App(){
  // 操作React DOM组件(原生html的一些标签)
  const imgData = {
    className: 'small',
    style: {
      width: '100px',
      height: '100px'
    }
  }

  return (
    <div>
      <img 
        src = {image}
        alt = "悬浮文字xxx"
        {...imgData}
        />  
    </div>
  )
}

export default App;
②自定义组件:function xxx

前面我们介绍了react主要有函数式组件与类组件,下面我将演示如何自定义函数式组件

javascript 复制代码
// 自定义组件
// 父组件向子组件传值:通过props,如:{text,active}
function Detail({text,active}){
  return (
    <>
      <p>{text}</p>
      <p>状态:{active ? '显示中' : '已经隐藏'}</p>
    </>
  )
}

function Article({title, content}) {
  return (
    <div>
      <div>{title}</div>
      <Detail 
        {...content}
      />
    </div>
  )
}

export default function App(){
  const articleData = {
    title: '文章标题',
    content:{
      'text': '文章内容',
      'active': true
    }
  }
  return (
    <>
      <Article
        {...articleData}
      />
    </>
  )
}
③实现插槽效果:children
  • JSX实现Vue插槽效果=》 children(简单版props,children被预先定义)
  • 运行下面的代码可以知道我把<li>等标签及内容页传给了List组件,实现了类似Vue插槽效果

App.js

javascript 复制代码
// JSX:实现类似Vue插槽效果:通过children传递(children这个属性是固定的)
function List({children}){
  return (
    <ul>{children}</ul>
  )
}

export default function App(){
  return (
    <>
      <List>
        <span>第一列</span>
        <li>列表项1</li>
        <li>列表项2</li>
        <li>列表项3</li>
      </List>
      <List>
        <span>第二列</span>
        <li>列表项a</li>
        <li>列表项b</li>
        <li>列表项c</li>
      </List>
    </>
  )
}
④子组件向父组件传值
  • 子组件向父组件传值=》自定义事件=》子组件触发事件来对应修改父组件(如果组件的值是可选的,那么需要在参数那赋值一个默认值,否则会有语法错误)
  • 同级组件传值=》常用做法:通过父组件做一个中转
  • 多级组件传值=》context=>createContext、useContext,通过Provider设置值
javascript 复制代码
import {useState} from 'react' //注意手动:引入时,不要忘记{}

// 子组件向父组件传值=》自定义事件=》子组件触发事件来对应修改父组件
function Detail(){
  const [status, setStatus] = useState(false)
  function changeStatus(){
    setStatus(!status)
  }
  return (
    <div>
      <button onClick={changeStatus}>点我有惊喜</button>
      <p style={{display:status?'block':'none'}}>大聪明~</p>
    </div>
  )
}

export default function App(){
  return (
    <div>
      <Detail />
    </div>
  )
} 

1.3 React Hooks

下面我将介绍React常用的几个钩子函数

  • react 函数组件的钩子函数(hook) hook函数是一个特殊的函数,目的是让函数组件也有类组件的特性. 类组件中可能一个周期函数中有多个业务逻辑代码,不利于维护. 类组件的学习成本相对较高,需要掌握es6的语法.
①useState:定义和更新组件的局部状态。
javascript 复制代码
import {useState} from 'react'
export default function App(){
  const [score, setScore] = useState(0)
  function handleClick(){
      setScore(score+1)
  }
  return (
    <div>
      <p>月薪:{score}K</p>
      <button onClick={handleClick}>点我升职加薪</button>
    </div>
  )
}
②useContext:可以避免props层层传递的情况,方便共享数据。
javascript 复制代码
import { createContext, useContext } from 'react';

export function Section({ children }) {
  const level = useContext(LevelContext)
  return (
    <section className='section'>
      <LevelContext.Provider value={level + 1}>
        {children}
      </LevelContext.Provider>
    </section>
  )
}

const LevelContext = createContext(0); // 默认值为0

export function Heading({ children }) {
  const level = useContext(LevelContext);
  switch (level) {
    case 1:
      return <h1>{children}</h1>
    case 2:
      return <h2>{children}</h2>
    case 3:
      return <h3>{children}</h3>
    case 4:
      return <h4>{children}</h4>
    case 5:
      return <h5>{children}</h5>
    default:
      throw new Error('Invalid heading level')
  }
}

export default function App() {
  return (
    <div>
      <Section>
        <Heading>主标题</Heading>
        <Section>
          <Heading>副标题</Heading>
          <Heading>副标题</Heading>
          <Heading>副标题</Heading>
          <Section>
            <Heading>子标题</Heading>
            <Heading>子标题</Heading>
            <Heading>子标题</Heading>
              <Section>
              <Heading>子子标题</Heading>
              <Heading>子子标题</Heading>
              <Heading>子子标题</Heading>
          </Section>
          </Section>
        </Section>
      </Section>
    </div>
  )
}
③useReducer:结合useReducer和dispatch可以实现类似Redux的状态管理。
javascript 复制代码
import {useReducer} from 'react'
export default function App(){
  function reducer(state, action){
    switch(action.type){
      case 'up':
        return state + 1

      case 'down':
        return state -1
    }
  }

  const cnts = 0
  //计数器
  const [state, dispatch] = useReducer(reducer, cnts)
  const handleIncre = () => dispatch({type:'up'})
  const handleDecre = () => dispatch({type:'down'})
  return (
    <div style={{padding:20}}>
      <button onClick={handleDecre}>-</button>
        <span> {state} </span>
      <button onClick={handleIncre}>+</button>
    </div>
  )
}
④useRef:引用之前的值、之前的标签、之前的组件
javascript 复制代码
import {useRef,useState} from 'react'
export default function App(){
  const [count, setCount] = useState(0)
  const prevCount = useRef()

  function handleClick(){
    //记录更新之前的count值
    prevCount.current = count
    setCount(count + 1)
  }
  return (
    <div style={{padding:20}}>
      <p>最新的count:{count}</p>
      <p>上一次的count:{prevCount.current}</p>
      <button onClick={handleClick}>增加</button>
    </div>
  )
}
⑤useEffect:主键加载时,可以设置一些"副作用"

后端的同学可以结合监视者设计模式来理解

javascript 复制代码
import {useEffect,useState} from 'react'
export default function App(){
  const [count, setCount] = useState(0)

  //监听count值,当变化时,执行一些操作,[]里表示监听那些数据
  useEffect(()=>{
    console.log('count变化了...do somethings');
  },[count])

  function handleClick(){
    setCount(count + 1)
  }
  return (
    <div style={{padding:20}}>
      <p>count:{count}</p>
      <button onClick={handleClick}>增加</button>
    </div>
  )
}
⑥useMemo:用于数据缓存,父组件的重新渲染会导致子组件的代码也重新执行,这时我们可以通过useMemo来缓存数据,通过[value]来指定当value值变化时,才会重新执行子组件的某个逻辑
javascript 复制代码
import { useMemo, useState } from 'react'
/*
  useMemo:缓存值,当监听的值有变化时,才会重新执行逻辑
  -- 本案例只有当输入框中的数字有变化时,才会重新执行复杂逻辑
*/
function DoSomeDiffcult({ value }) {
  //缓存第一次的result值,后续只有当传入函数的value值变化时,才会重新执行逻辑,否则直接从缓存中取值返回
  const result = useMemo(() => {
    let result = 0
    console.log('执行系列复杂计算....');
    result =  value * 24124142
    return result;
  }, [value])

  return (
    <div>
      <p>输入内容:{value}</p>
      <p>经过复杂处理后的数据:{result}</p>
    </div>
  );
}

function App() {
  const [count, setCount] = useState(0)
  const [inputValue, setInputValue] = useState(3)
  return (
    <div style={{ padding: 20 }}>
      <p>count的值为:{count}</p>
      <button onClick={() => setCount(count + 1)}>点击更新</button>
      <br/>
      <br/>
      <input type='number' 
       value={inputValue} 
       onChange={e => setInputValue(parseInt(e.target.value))}
      ></input>
      <DoSomeDiffcult value={inputValue} />
    </div>
  )
}

export default App;
⑦useCallback:用于函数缓存,memo记忆子组件+useCallback,保证传入的函数是同一个,从而控制子组件不重新渲染
javascript 复制代码
import { memo, useState, useCallback } from 'react'
/*
  useCallback: 缓存函数
  -- 通过useCallback保证传入的是同一个handleClick,防止每次父组件更新,子组件都被迫更新
  -- 当触发子组件的click事件时,才会更新
*/
const Button = memo(function({onClick}){
  console.log('button 被渲染了');
  return <button onClick={onClick}>子组件</button>
})

function App() {
  const [count, setCount] = useState(0)
  const handleClick = useCallback(() => {
    console.log("点击按钮");
  },[]);

  return (
    <div style={{ padding: 20 }}>
      <p>count的值为:{count}</p>
      <button onClick={() => setCount(count + 1)}>点击更新</button>
      <br/>
      <br/>
      <Button onClick={handleClick}></Button>
    </div>
  )
}

export default App;

2 实战篇

了解了React的核心概念之后,下面进入demo环节。通过我们学习的知识实现一个todoList代办列表。
全部代码https://github.com/ziyifast/front-demo/tree/main/2-my-todo-list
最终项目结构:

2.1 创建项目

bash 复制代码
# 创建项目,然后根据提示设置自己所需要的内容
npx create-next-app@latest

# 创建完成之后,cd 进入我们的项目,执行npm run dev启动项目
npm run dev 

2.2 分析&创建types&components

我们要实现一个代办事项

  1. types.ts:一个代办项包括它的内容text,它的状态completed,以及它的id。
  2. components:自定义所需组件
  • AddTodo.tsx:添加代办模块
  • TodoFilter.tsx:过滤模块(全部、已完成、未完成)
  • TodoItem.tsx:单个代办项(包含text、删除按钮、已完成按钮)
  • TodoList.tsx:页面主体(展示代办项)
  1. pages.tsx:组装组件,将自定义组件放入主页面
①types.ts
js 复制代码
// 定义代办项
export interface Todo {
    id: number;
    text: string;
    completed: boolean;
}
②components:定义页面所需组件

创建components文件夹,在该文件夹下创建页面所需组件

  • AddTodo.tsx:添加代办模块
  • TodoFilter.tsx:过滤模块(全部、已完成、未完成)
  • TodoItem.tsx:单个代办项(包含text、删除按钮、已完成按钮)
  • TodoList.tsx:页面主体(展示代办项)
1. AddTodo.tsx:添加代办模块
js 复制代码
import React, { useState } from "react"

interface AddTodoProps {
    addTodo: (text: string) => void
}
export function AddTodo({addTodo}: AddTodoProps){
    const [text, setText] = useState('')
    //处理提交请求
    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) =>{
        //阻止表单默认行为
        e.preventDefault()
        addTodo(text)
        //提交之后将输入框置空
        setText('')
    }

    return (
        <form onSubmit={handleSubmit}>
            <input 
            aria-label="新建代办"
            type="text" 
            value={text} 
            onChange={(e) => setText(e.target.value)}

            />
            <button type="submit">新建代办</button>
        </form>
    )
}
2. TodoFilter.tsx:过滤模块(全部、已完成、未完成)
javascript 复制代码
export default function TodoFilter({setFilter}:any){
    return (
        <div>
            <button onClick={()=>setFilter('all')}>All</button>
            <button onClick={()=>setFilter('completed')}>Completed</button>
            <button onClick={() => setFilter('active')}>active</button>
        </div>
    )
}
3. TodoItem.tsx:单个代办项(包含text、删除按钮、已完成按钮)
javascript 复制代码
export function TodoItem({todo, toggleTodo, deleteTodo}:any) {
    return (
        <li style={{textDecoration: todo.completed ? 'line-through' : 'none'}}>
            {todo.text}
            <button onClick={() => toggleTodo(todo.id)}>切换</button>
            <button onClick={() => deleteTodo(todo.id)}>删除</button>
        </li>
    )
}
4. TodoList.tsx:页面主体(展示代办项)
javascript 复制代码
import { Todo } from "@/types"
import { TodoItem } from "./TodoItem";

interface TodoListProps {
    todos: Array<Todo>;
    deleteTodo: (id: number) => void;
    toggleTodo: (id: number) => void;
}


export default function TodoList({ todos, deleteTodo, toggleTodo }: TodoListProps) {
    return (
        <ul>
            {todos.map(todo => (
                <TodoItem key={todo.id} todo={todo} deleteTodo={deleteTodo} toggleTodo={toggleTodo} />  
            ))}
        </ul>
    )
}
③page.tsx
javascript 复制代码
"use client";
import Image from "next/image";
import styles from "./page.module.css";
import TodoFilter from "@/components/TodoFilter";
import TodoList from "@/components/TodoList";
import { AddTodo } from "@/components/AddTodo";
import { useState } from "react";
import { Todo } from "@/types";


export default function Home() {
  //初始代办事项为空
  const [todos, setTodos] = useState<Todo[]>([])
  const [filter, setFilter] = useState('all')
  const addTodo = (text: string) => {
    const newTodoItem = {
      id: Date.now(),
      text,
      completed: false
    }
    setTodos([...todos, newTodoItem])
  }

  const deleteTodo = (id: number) => {
    setTodos(todos.filter(todo => todo.id !== id))
  }

  const toggleTodo = (id: number) => {
    setTodos(todos.map(todo => {
      if (todo.id === id) {
        todo.completed = !todo.completed
      }
      return todo
    }))
  }

  const getFilterTodo = () => {
    switch (filter) {
      case 'completed':
        return todos.filter(todo => todo.completed)
      case 'active':
        return todos.filter(todo => !todo.completed)
      default:
        return todos
    }
  }

  return (
    <div>
      <h1>my-todo-list</h1>
      <AddTodo addTodo={addTodo}></AddTodo>
      <TodoList todos={getFilterTodo()} deleteTodo={deleteTodo} toggleTodo={toggleTodo}></TodoList>
      <TodoFilter setFilter={setFilter}></TodoFilter>
    </div>
  );
}
④效果

页面效果很简陋,主要给大家展示用法,后续大家可以引入第三方样式或者自定义样式来美化

bash 复制代码
# 进入并运行项目
cd 2-my-todo-list/
npm run dev 

页面效果:

相关推荐
吾与谁归in13 分钟前
【C#学习——特性】
开发语言·c#
code monkey.14 分钟前
【探寻C++之旅】第一章:C++入门
开发语言·c++
励志成为大佬的小杨30 分钟前
c语言中的枚举类型
java·c语言·前端
yava_free33 分钟前
指定Bean加载顺序的能力
java·开发语言
程序员老冯头34 分钟前
第二十三章 C++ 继承
开发语言·数据结构·c++·算法·继承
whisperrr.39 分钟前
探索JDBC:Java数据库连接的艺术与魅力
java·开发语言·数据库
非凡的世界1 小时前
使用PHP函数 “setcookie“ 设置cookie
开发语言·php
soragui1 小时前
【Ubuntu】如何轻松在Apache服务器上部署Laravel博客系统
开发语言·个人开发
前端熊猫1 小时前
Element Plus 日期时间选择器大于当天时间置灰
前端·javascript·vue.js
van叶~1 小时前
仓颉语言实战——1. 类型
开发语言·windows·python·仓颉