React18极客园
react18+极客园项目:https://www.bilibili.com/video/BV1ZB4y1Z7o8/?vd_source=033e18a7971697a5b8192da1e492326e
文档:https://www.yuque.com/fechaichai/qeamqf/xbai87#1ba02eb3
文档:https://www.yuque.com/fechaichai/tzzlh1
代码:https://gitee.com/libudding/chaichai1_react-basic/blob/master/public/index.html
React组件基础
jsx区别
1.变量
Vue = 双大括号 {{ }}
React = 单大括号 { }
原生 = 啥括号都不能直接写
在 JSX 里,所有 JavaScript 表达式,必须用 { } 包起来。
- HTML 标签、文字 → 直接写
<div>我是文字</div> 原生、 react、vue都支持- JS 变量、JS 对象、JS 表达式 → 必须包 { }
{ 变量 } { 1 + 1 } { 函数调用() } { { a: 1, b: 2 } } // ← 这就是对象// 两次{} const songs = [ { id: 1, name: '痴心绝对' }, { id: 2, name: '像我这样的人' }, { id: 3, name: '南山南' } ] function App() { return ( <div className="App"> <ul> { songs.map(item => <li>{item.name}</li>) } </ul> </div> ) } export default App2.className
- className 可以接受字符串
- className=“App”
- 类名 - className - 动态类名控制
- className={ showTitle ? ‘title’ : ‘’}
3.style
- Vue、原生:style=“color:red” ✅ 字符串可以
- style 必须接受一个对象,不支持字符串
React:style=“color:red” ❌ 直接报错
React 只认:style={{ color: ‘red’ }}
jsx注意事项
JSX必须有一个根节点,如果没有根节点,可以使用<></>(幽灵节点)替代
JSX支持多行(换行),如果需要换行,需使用() 包裹,防止bug出现
情况 A:一行写完 → 不需要 ()
return <div>一行</div>情况 B:多行换行 → 推荐必须加 ()
return ( <div> <p>多行</p> </div> )因为 JS 有一个机制:自动在行尾加分号 ;
JSX可以不驼峰命名吗?绝对不行!必须驼峰!因为 JSX 本质是 JS,不是 HTML。在 JS 里,属性名不能有横杠 -,会被当成减法。
import React from 'react' import ReactDOM from 'react-dom' import './index.css' // 引入根组件App import App from './App' // 通过调用ReactDOM的render方法渲染App根组件到id为root的dom节点上 ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ) function App() { return ( <div> <div>this is a div</div> </div> ) } export default App函数组件
- 组件的名称必须首字母大写
- 使用函数名称作为组件标签名称
类组件
- 类名称也必须以大写字母开头
- 类组件应该继承 React.Component 父类,从而使用父类中提供的方法或属性
- 类组件必须提供 render 方法render 方法必须有返回值,表示该组件的 UI 结构
组件状态
class Counter extends React.Component { // 定义数据 state = { count: 0 } // 定义修改数据的方法 setCount = () => { this.setState({ count: this.state.count + 1 }) } // 使用数据 并绑定事件 render () { return <button onClick={this.setCount}>{this.state.count}</button> } }class HelloFnClass extends React.Component { state = { count: 0, list: [1, 2, 3], person: { name: 'jack', age: 18 } } changeCount = () => { this.setState({ count: this.state.count + 1, list: [...this.state.list, 4], person: { ...this.state.person, name: 'rose' } }, () => { console.log('最新值:', this.state) }) console.log('不是最新值:this.state) } render () { return <div onClick={this.changeCount}>计数器:{this.state.count}</div> } } function App () { const name = true return ( <div> <HelloFnClass></HelloFnClass> </div> ) } export default App函数组件 + useState + useEffect简单举例
useState 永远返回数组,不管是 CDN 还是脚手架。
import { useState } from 'react' function App() { // 定义状态:count 变量,setCount 修改它的方法 const [count, setCount] = useState(0) return ( <div> <p>点击了 {count} 次</p> <button onClick={() => setCount(count + 1)}>+1</button> </div> ) } export default Appimport { useState, useEffect } from 'react' function App() { const [count, setCount] = useState(0) // 一进页面就执行(像 mounted) useEffect(() => { console.log('组件加载完成啦') }, []) // ↑ 空数组 = 只执行一次 // count 变了就执行(像 watch)加载执行一次 + 变化再执行 useEffect(() => { console.log('count 变成了:', count) }, [count]) return ( <div> <p>{count}</p> <button onClick={() => setCount(count + 1)}>+1</button> </div> ) } export default App 啥都不写 → 每次渲染都执行 组件加载完成啦 ← 页面刚进来 count 变成了:0 ← 页面刚进来 count 变成了:1 ← 点第1下 count 变成了:2 ← 点第2下 count 变成了:3 ← 点第3下为什么 React 里推荐写成箭头函数
- 类组件必用,防止 this 丢失
- 函数组件里根本没有 this,所以根本不存在 “this 丢失” 这一说。
普通函数在严格模式下,this 就是 undefined
React 内部默认跑在 严格模式 ‘use strict’ 下。
在严格模式里:普通函数独立运行时,this = undefined
function App() { console.log(this) // undefined } App()因为类组件是 class 实例化 调用的:
const app = new App() app.render() // 这里 this 指向 app 实例所以类组件才有 this,函数组件没有。
函数组件的事件绑定-传递参数
- onClick 必须放一个函数!
- onClick={ fn } 正确 → 给的是函数
- onClick={ fn() } 错误 → 给的是函数执行结果
- 想传参 → 必须套箭头函数:onClick={() => fn(参数)}
- vue支持直接传递<button @click=“onDel(id)”>删除
onClick={ onDel } -> onClick={ () => onDel(id) } 注意: 一定不要在模板中写出函数调用的代码 onClick = { onDel(id) } <button onClick={(e) => onDel(e, item.id)}>x</button> 如果啥都不传 直接在onDel函数中也能打印到e组件状态useState
你完全可以在函数组件里写普通变量
function App() { // 这是一个普通变量 let count = 0 const add = () => { count = count + 1 console.log(count) // 这里确实会变! } return ( <div> <p>{count}</p> <button onClick={add}>+1</button> </div> ) }console 会输出 1、2、3…
但页面上的数字永远不动!
因为:
React 不知道你改了数据!
React 只有在这几种情况才会重新刷新页面:
- state 变化(useState 里的东西)
- props 变化
- 父组件刷新
你改的是一个普通 let 变量React 根本不监听它它变了 React 也不知道,所以页面不更新。
那 useState 到底干了啥?
它干了两件事:
- 帮你把数据存起来(不会随着渲染丢失)
- 数据一改,自动触发页面重新渲染
函数组件每次渲染,都会把里面的变量全部重新执行一遍!
普通变量会被 “重置”,useState 会帮你把值 “记住”。
function App() { // 每次渲染都会重新执行这行! let count = 0 const add = () => { count = count + 1 console.log(count) } return ( <div> <button onClick={add}>+1</button> </div> ) }你点击按钮 → 组件会重新渲染一次 一渲染 → 又跑了一遍:let count = 0
结果:
你刚把 count 改成 1
一渲染,又被重置成 0
永远存不住!
这就是:
普通变量会随着渲染丢失,记不住上一次的值!
const [count, setCount] = useState(0)useState 会:
第一次渲染:初始化 count = 0
后面每次渲染:
不再执行 useState (0) 重新初始化
直接把上一次的值拿出来给你
你改了之后,它帮你存在 React 内部
下次渲染还能拿到
点击 → count 变成 1
重新渲染 → count 还是 1
再点 → 变成 2
永远不会丢!
普通变量:每次刷新都重置 → 记不住
useState:存在 React 肚子里 → 能记住
这就是:
帮你把数据存起来,不会随着渲染丢失
函数组件每更新一次,整个函数都会重新跑一遍!
普通变量会被重新创建,useState 会从 React 内部读取上一次的值,不会重置。
React hook
React Hook = 给函数组件用的 “功能工具函数”
名字都以 use 开头。
- 它是干嘛的?
在 Hook 出现之前,函数组件很弱:
不能存状态
不能发请求
不能做生命周期
Hook 就是用来补全这些功能的,让函数组件也能:
存数据(useState)
做副作用 / 请求(useEffect)
拿 props/context(useContext)
缓存逻辑(useMemo/useCallback) - 哪些是 Hook?
useState
useEffect
useContext
useRef
useMemo
useCallback
useReducer
自定义Hook(以 use 开头的函数)
state
不要直接修改state中的值,必须通过setState方法进行修改
class Counter extends React.Component { // 定义数据 state = { count: 0 } // 定义修改数据的方法 setCount = () => { this.setState({ count: this.state.count + 1 }) } // 使用数据 并绑定事件 render () { return <button onClick={this.setCount}>{this.state.count}</button> } }受控组件(类、函数)
使用hook的写法写受控组件
import React, { useState } from 'react' function Demo() { const [msg, setMsg] = useState('') return ( <div> <input type="text" value={msg} onChange={(e) => setMsg(e.target.value)} /> </div> ) }使用类的写法写受控组件
class HelloFnClassInput extends React.Component { state = { msg: '' } changeCount = (e) => { this.setState({ msg: e.target.value }) } render () { return ( <div> <div>{this.state.msg}</div> <input value={this.state.msg} onChange={this.changeCount} /> </div> ) } } function App () { return ( <div> <HelloFnClassInput /> </div> ) } export default App非受控组件-类组件createRef + ref 属性获取 DOM
非受控组件就是通过手动操作dom的方式获取文本框的值,文本框的状态不受react组件的state中的状态控制,直接通过原生dom获取输入框的值
import React, { Component, createRef } from 'react' class Demo extends Component { // 2. 创建 ref 对象,挂载到实例上 msgRef = createRef() handleGet = () => { // 4. 通过 current 拿 DOM 元素 console.log(this.msgRef.current.value) } render() { return ( <div> {/* 3. 把 ref 绑到 input 上 */} {/* <div>{this.msgRef.current ? this.msgRef.current.value : ''}</div>不会实时同步,只有state才行 */} <input type="text" ref={this.msgRef} onChange={this.handleGet}/> <button onClick={this.handleGet}>获取值</button> </div> ) } } export default Demo createRef() 创建一个容器 ref={xxx} 把 DOM 放进这个容器 容器里的 .current 就是真实 DOM 元素 .value 就是输入框的值非受控组件-函数组件 Hooks 写法(useRef)
import React, { useRef } from 'react' // 1. 导入 useRef 钩子 function Demo() { // 2. 调用 useRef 创建 ref 对象,和 createRef 用法几乎一样 const msgRef = useRef(null) const handleGet = () => { // 4. 同样通过 .current 拿到 DOM 和值 console.log(msgRef.current.value) } return ( <div> {/* 3. 给 input 绑定 ref,和类组件写法完全一样 */} <input type="text" ref={msgRef} /> <button onClick={handleGet}>获取值</button> </div> ) } export default DemoReact组件通信
- 父子关系 - props
- 兄弟关系 - 自定义事件模式产生技术方法 eventBus / 通过共同的父组件通信
- 其它关系 - mobx / redux / zustand
父传子
子组件中通过 props 接收父组件中传过来的数据
a. 类组件使用this.props获取props对象
b. 函数式组件直接通过参数获取props对象
import React from 'react' // 函数式子组件 function FSon(props) { console.log(props) return ( <div> 子组件1 {props.msg} </div> ) } // 类子组件 class CSon extends React.Component { render() { return ( <div> 子组件2 {this.props.msg} </div> ) } } // 父组件 class App extends React.Component { state = { message: 'this is message' } render() { return ( <div> <div>父组件</div> <FSon msg={this.state.message} /> <CSon msg={this.state.message} /> </div> ) } } export default App直接给普通标签加 msg={xxx} 是不行的,这是 React 组件和原生 HTML 标签的核心区别。
传自定义数据的 1种正确写法
<div>子传父父组件给子组件传递回调函数,子组件调用
import './app.css' import React from 'react' function Fson (props) { return ( {/* 注意调用的时候不能直接传递值,要写一个函数 */} <div onClick={() => props.changeMessage('测试2')}> {props.msg} </div> ) } class Cson extends React.Component { render () { return ( {/* 注意调用的时候不能直接传递值,要写一个函数 */} <div onClick={() => this.props.changeMessage('测试1')}> {this.props.msg} </div> ) } } class App extends React.Component { state = { message: 'this is message' } changeMessage = (newMsg) => { this.setState({ message: newMsg }) } render () { return ( <div> {/* 注意传递时候不要用函数接传递值,直接传过去 */} <Fson msg={this.state.message} changeMessage={this.changeMessage}></Fson> <Cson msg={this.state.message} changeMessage={this.changeMessage}></Cson> </div> ) } } export default App
兄弟组件通信
通过状态提升机制,利用共同的父组件实现兄弟通信
以上两种
跨组件通信Context
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法
1- 创建Context对象 导出 Provider 和 Consumer对象
const { Provider, Consumer } = createContext()
2- 使用Provider包裹上层组件提供数据
<Provider value={this.state.message}> {/* 根组件 */} </Provider>
3- 需要用到数据的组件使用Consumer包裹获取数据
<Consumer > {value => /* 基于 context 值进行渲染*/} </Consumer>
import './app.css' import React, { createContext } from 'react' const { Provider, Consumer } = createContext() function Fson () { return ( <div> <div>zi</div> <Cson></Cson> </div> ) } class Cson extends React.Component { render () { return ( <Consumer> {value => ( <div> <div>sun</div> <div>{value}</div> </div> )} </Consumer> ) } } class App extends React.Component { state = { message: 'this is message' } render () { return ( <Provider value={this.state.message}> <div> <div>fu</div> <Fson></Fson> </div> </Provider> ) } } export default App
React组件进阶
children属性
children 就是组件标签中间的内容
组件里用 props.children 接收并显示
<MyComponent> 我是中间的内容 👈 这个就是 children </MyComponent> function MyComponent(props) { return <div>{props.children}</div> }
props校验
props 校验,写在【子组件】身上!
子组件:我规定别人传什么给我
父组件:我按规定传给子组件
- 安装属性校验包:yarn add prop-types
- 导入prop-types 包
- 使用 组件名.propTypes = {} 给组件添加校验规则
