React快速入门
全部代码:https://github.com/ziyifast/front-demo
- React特点:
- 声明式设计:声明范式
- 高效:使用VDOM,减少DOM的交互
- 灵活:与已知的库或框架完好配合
- JSX:一种独立的语言,试图解决很多JS的缺陷,ES6包含了几乎所有JSX的特性
- 组件:代码复用
- 单向响应数据流:比双向绑定更简单,更快。
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;
③数据渲染
数据渲染主要有:
- 条件渲染(if)
- 列表渲染(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;
④事件处理
- 定义function
- 通过{funciton_name}引用
- 标签内绑定事件,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
我们要实现一个
代办事项
:
- types.ts:一个代办项包括它的内容text,它的状态completed,以及它的id。
- components:自定义所需组件
- AddTodo.tsx:添加代办模块
- TodoFilter.tsx:过滤模块(全部、已完成、未完成)
- TodoItem.tsx:单个代办项(包含text、删除按钮、已完成按钮)
- TodoList.tsx:页面主体(展示代办项)
- 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
页面效果: