1. 搭建环境
npx create-react-app react-basic-demo
# 项目启动
npm start
2. 基本使用
JSX 语法将 html 和 js 写在一起。插值语法使用{}
识别 JavaScript 中的表达式,比如基本数据类型常量、变量、函数调用、方法调用等,可以写在标签内容或者标签属性。
if、switch、变量声明等属于语句,不是表达式,不能写在 {} 内部。可以使用函数封装或者三元表达式。
const flag = true
return <div>
{flag && <p>hello</p>}
{flag ? <p>你好</p> : <p>再见</p>}
</div>
或者用函数封装:
function Hello() {
if (flag) return <p>你好</p>
else return <p>再见</p>
}
return <Hello></Hello>
非要在 {} 里面使用 引用类型,可以将其序列化再使用。
// jsx 标签数组在return 时会自动展开
const arr = [
(<p>hello</p>),
(<h1>REACT</h1>)
]
// ...
return (
<div>
Hello World
{arr}
</div>
);
- 首字母小写 - HTML tag
- 首字母大写 - 自定义组件
JSX 里的标签必须是闭合的,<input>
<br>
这样写在 JSX 会报错(在 HTML 中不会报错),必须闭合 <input/>
每一段 JSX 只能有一个根节点,或者使用 <></>
( Fragment )
属性和 HTML 属性基本一样,但有些和 JS 关键字冲突了:
class
要改为className
style
要写成 JS 对象(不能是 string),key 采用驼峰写法for
要改为htmlFor
列表渲染使用 map 。
事件绑定用;on + 事件名称 = { 事件处理函数/程序 }
小驼峰命名法
事件回调函数中可传形参 e 是该事件。
传递自定义参数时,直接使用传递参数的函数就成了函数的直接调用。
需要将事件绑定的位置改为箭头函数的写法。也就是将函数的调用作为返回值去使用。
想支持泛型函数可能会报错,因为他把<T>
,理解成了是一个元素<div>
,纠正泛型<T,>
即可。
注意 TS 的写法
function clickHandler(event: React.MouseEvent<HTMLParagraphElement>) {
event.preventDefault()
console.log('clicked')
}
return <p onClick={clickHandler}>hello world</p>
如果要想传递参数,可以通过如下方式
import type { MouseEvent } from 'react';
function clickHandler(event: React.MouseEvent<HTMLParagraphElement>, x: string) {
event.preventDefault()
console.log('clicked', x)
}
return (
<p onClick={(e: React.MouseEvent<HTMLParagraphElement>) => clickHandler(e, 'hello')}>
hello world
</p>
)
PS:Event handlers must be passed, not called! onClick={handleClick}
, not onClick={handleClick()}
.
return 返回值要用()
括起来(除非返回值只有一行)。
并且返回值中只能返回一个根元素。(可以使用<></>
或<Fragment></Fragment>
作为最外层的根元素,其中 Fragment 可以写遍历循环中id的值)。
jsx 语法的本质是调用了 React.createElement 方法。createElement(type, props, ...children)
function Greeting({ name }) {
return createElement(
'h1',
{ className: 'greeting' },
'你好'
);
}
-
type:type 参数必须是一个有效的 React 组件类型,例如一个字符串标签名(如 ‘div’ 或 ‘span’),或一个 React 组件(一个函数式组件、一个类式组件,或者是一个特殊的组件如 Fragment)。
-
props:props 参数必须是一个对象或 null。如果你传入 null,它会被当作一个空对象。创建的 React 元素的 props 与这个参数相同。注意,props 对象中的 ref 和 key 比较特殊,它们 不会 作为 element.props.ref 和 element.props.key 出现在创建的元素 element 上,而是作为 element.ref 和 element.key 出现。
-
可选 …children:零个或多个子节点。它们可以是任何 React 节点,包括 React 元素、字符串、数字、portal、空节点(null、undefined、true 和 false),以及 React 节点数组。
React 无法通过 return false 来阻止默认行为,只能通过 e.preventDefault() 来阻止默认行为。
const handleSubmit = (e) => {
// e.preventDefault()
console.log('click submit')
}
return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
);
e.nativeEvent 用来获取原生事件对象。
3. 组件
进行数据类型检查:proptypes
function
React 中,一个组件就是一个首字母大写的函数。
传参:
<form onSubmit={ handleSubmit('arg')}>
直接传参相当于函数的直接调用,可以改为箭头函数。
function App() {
const handleSubmit = (arg, e) => {
e.preventDefault()
console.log(arg)
}
return (
<form onSubmit={e => handleSubmit('arg', e)}>
<button type="submit">Submit</button>
</form>
);
}
class
此外,还有类组件。
import React from "react";
class App extends React.Component {
handleClick() {
console.log('click')
}
render() {
return (
<button onClick={this.handleClick}>Click</button>
)
}
}
但是这样如果在 handleClick 方法内使用this 就会存在 找不到this的情况,this 值为 undefined。所以我们可以有以下几种方式修正它:
- 改为箭头函数(事件绑定时或者触发的回调函数时)
handleClick= ()=> {
console.log(this)
}
或者是:
<button onClick={() => this.handleClick()}>Click</button>
- 使用 bind 提前绑定 this 的指向
constructor() {
super();
this.handleClick = this.handleClick.bind(this)
}
传参 的两种形式:
- 箭头函数
- bind
// <button onClick={ this.handleClick.bind(this, 'arg') }>Click</button>
<button onClick={e => this.handleClick('arg')}>Click</button>
响应式修改数据:
class App extends React.Component {
constructor() {
super();
this.state = {
num: 1
}
}
handleClick = () => {
this.setState({
num: this.state.num + 1
})
}
render() {
return (
<>
{this.state.num}
<button onClick={this.handleClick}>state ++</button>
</>
)
}
}
如果同时多个 setState 修改,由于 setState 是异步的,所以结果就是只被修改了一次:
handleClick = () => {
this.setState({
num: this.state.num + 1
})
this.setState({
num: this.state.num + 1
})
this.setState({
num: this.state.num + 1
})
}
以下写法可以解决这个问题(虽然存在回调地狱):
handleClick = () => {
this.setState({
num: this.state.num + 1
},()=> {
this.setState({
num: this.state.num + 1
}, ()=> {
this.setState({
num: this.state.num + 1
})
})
})
}
或者这种写法也可以:
handleClick = () => {
// 返回值为对象,需要用()括起来,否则默认是函数体
this.setState((cur)=> ({
// cur 是上次的值 等价于 this.state
num: cur.num + 1
}))
this.setState((cur)=> ({
num: cur.num + 1
}))
this.setState((cur)=> ({
num: cur.num + 1
}))
}
如果要改变状态的代码是处于某个 html 元素的事件中,则它是异步的,否则就是同步的
handleClick = () => {
this.setState({
num: this.state.num + 1
})
console.log(this.state.num, 'num')
}
所以以上代码先执行同步代码console.log(this.state.num, 'num')
,然后执行异步代码num: this.state.num + 1
。
4. useState
useState 向组件中添加状态变量
状态是只读的,不可以直接修改
对于对象类型的状态变量,应该传递一个新的对象来更改
需要对象展开,并重新赋值,进行增加或者修改。
如果需要删除,则使用 filter。
import { useState } from "react";
function App() {
const [arr, setArr] = useState([
{ id: 1, name: 'zhangsan' },
{ id: 2, name: 'lisi' },
{ id: 3, name: 'wangwu' }
]);
const content = arr.map((item, index) => {
return <li key={item.id}>{item.name}</li>
})
const deleteVar = () => {
setArr(arr.filter(item => item.name !== 'zhangsan'))
}
return (
<>
<ul>
{content}
</ul>
<button onClick={deleteVar}>点击删除zhangsan</button>
</>
)
}
export default App
案例小总结
// 创建 react 实例
const root = ReactDOM.createRoot(document.getElementById('root'));
// 渲染根组件 <React.StrictMode> 为严格模式
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// 函数式组件 - 一个函数就是一个组件
import {Fragment, useState} from "react";
function App() {
// const divContent = '标签内容'
const divTitle = '标签标题'
const flag = true
let divContent = null
const list = [
{id: 1, name: 'a'},
{id: 2, name: 'b'},
{id: 3, name: 'c'}
]
// 列表渲染,看清楚 这里返回用的是 () 哦
const listContent = list.map(item => (
// 存在多个跟标签,且需要 key 时
<Fragment key={item.id}>
<li>{item.name}</li>
<li>-----------</li>
</Fragment>
))
// 直接将 js 和 html 混写
if (flag) {
divContent = <span>flag为true</span>
} else {
divContent = <span>flag为false</span>
}
// 类似于 vue 响应式的数据状态更新机制
// 不能直接渲染对象数据,只能渲染其属性
const [content,setContent] = useState('默认内容')
const [data, setData] = useState({
name: 'xx',
age: 13
})
const handleClick = (e) => {
console.log('点击了按钮',e)
setContent('新内容')
// 不能局部修改,而是直接替换,将展开对象写在最前面
setData({
...data,
age: 18
})
}
return (
// 只能返回一个根元素 可以使用空标签<></>
<>
{/* {} 动态属性(内容) 如果是{""}则为静态属性(内容) */}
<div title={divTitle}>
{divContent}
</div>
<ul>{listContent}</ul>
{/* onClick 默认格式 */}
<button onClick={handleClick}>触发事件</button>
<div>
{content} - {data.name} - {data.age}
</div>
</>
);
}
export default App;
5. 修改样式
function App() {
const dataObj = {
className: "small",
style: {
width: "200px",
height: 200,
background: 'grey'
}
}
return (
<>
<div>
{/* {} 一个是本身react 的语法 另一个是代表一个全是css 键值的对象 可以写成多种形式 */}
{/*<img src={logo} alt="" className="small" style={{width: 100, height: '100px', backgroundColor: 'lightcyan'}}/>*/}
<img src={logo} alt="" {...dataObj} />
</div>
</>
)
}
{…dataObj} 是 react 的特殊写法,与ES6中的展开运算法不是一回事。
function App() {
const imgStyleObj = {
width: "200px",
height: 200,
background: 'grey'
}
return (
<>
<div>
<img src={logo} alt="" className="small" style={imgStyleObj} />
</div>
</>
)
}
可以使用 classnames 这个库方便进行 动态控制 class 类名
多个类名:
6. 获取 DOM
渲染 html(类似于 Vue 中的 v-html)
在 vue 中渲染 html或者 svg 需要使用 v-html,而在react 中需要:
<div dangerouslySetInnerHTML={{__html: captcha}}></div>
captcha 这里是包含一段 html 的代码片段字符串。
7. 组件通信
父传子 - props
function Article(props) {
return (
<div>
<h2>{props.title}</h2>
<p>{props.content}</p>
<p>状态: {props.active ? '显示' : '隐藏'}</p>
</div>
)
}
// 使用 defaultProps 设置默认值,当没有父组件的值传入时使用默认值
Article.defaultProps = {
title: '默认title',
content: '默认content'
// ...
}
function App() {
return (
<>
<Article title="标题1" content="内容1" active/>
<Article title="标题2" content="内容2"/>
</>
);
}
export default App;
// 设置默认值也可以写为,作为类的一个静态属性:
static Article.defaultProps = {
title: '默认title',
content: '默认content'
// ...
}
父传子 - 插槽
function Article({children, title, footer=<div>默认底部</div>}) {
return (
<>
<h1>{title}</h1>
<div>
{children}
</div>
{footer}
</>
)
}
function App() {
return (
<>
<Article title="文章1" footer={<p>这是底部内容1</p>}>
<h2>标题1</h2>
<p>内容1</p>
</Article>
<Article title="文章2" footer={<p>这是底部内容2</p>}>
<h2>标题2</h2>
<p>内容2</p>
</Article>
</>
);
}
export default App;
子传父 - 自定义事件
子传父 => 状态提升
import {useState} from "react";
function Detail({onActive}) {
const [status, setStatus] = useState(false)
function handleClick() {
setStatus(!status)
onActive(status)
}
return (
<div>
<button onClick={handleClick}>点击</button>
<p style={{
display: status ? 'block' : 'none'
}}>Detail 的内容</p>
</div>
)
}
function App() {
const handleActive = (status) => {
console.log(status)
}
return (
<>
{/* 父组件接收子组件通过自定义事件 */}
<Detail
onActive={handleActive}
/>
</>
);
}
export default App;
多层级通信 - useContext
官方文档:useContext
用法:
- createContext 创建一个上下文对象 MyContext 并返回
- 组件内传递。可以通过将上下文对象 MyContext 传给 useContext() 来读取上下文的值(比如下面的 2 )
- 组件间传递。在顶层组件 MyContext.Provider 组件提供数据,在底层组件 useContext 钩子获取消费数据
import {createContext, useContext, useState} from "react";
function Section({children}) {
// 2. 顶层组件 MyContext.Provider 组件提供数据
const level = useContext(LevelContent)
return (
<section className="section">
{/*逐渐从上层提供的LevelContext中取值*/}
<LevelContent.Provider value={level + 1}>
{/*children 也就是 heading 需要使用 useContext*/}
{children}
</LevelContent.Provider>
</section>
);
}
function Heading({children}) {
// 3. 底层组件 useContext 钩子获取消费数据
const level = useContext(LevelContent)
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>;
case 6:
return <h6>{children}</h6>;
default:
throw Error('未知的 level:' + level);
}
}
// 1. createContext 创建一个上下文对象
const LevelContent = createContext(0)
function App() {
return (
<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>
)
}
export default App;
组件通信小案例 - 汇率转换:
App.jsx
import React, {Component} from 'react';
import Money from "./components/Money";
class App extends Component {
state = {
dollar: '',
rmb: ''
}
transformToRMB = (value) => {
if (parseFloat(value) || value === "" || parseFloat(value) === 0) {
this.setState({
dollar: value,
rmb: value === "" ? "" : (value * 7.3255).toFixed(2)
})
} else {
alert('请输入数字')
}
}
transformToDollar = (value) => {
if (parseFloat(value) || value === "" || parseFloat(value) === 0) {
this.setState({
dollar: value === "" ? "" : (value / 7.3255).toFixed(2),
rmb: value
})
} else {
alert('请输入数字')
}
}
render() {
return (
<div>
<Money text="美元" money={this.state.dollar} transform={this.transformToRMB}/>
<Money text="人民币" money={this.state.rmb} transform={this.transformToDollar}/>
</div>
);
}
}
export default App;
Money.jsx
import React from 'react';
function Money(props) {
const handleChange = (e) => {
// 将子组件的值传递给父组件 e.target.value 获取输入框的值
props.transform(e.target.value)
}
return (
<>
<fieldset>
<legend>{props.text}</legend>
<input type="text" value={props.money} onChange={handleChange}/>
</fieldset>
</>
);
}
export default Money;
受控组件,本质上其实就是将表单中的控件和视图模型(状态)进行绑定,之后都是针对状态进行操作。
案例:
- 一个基本的受控组件
- 对用户输入的内容进行限制
- 文本域
- 单选与多选框
- 下拉列表
8. useEffect
useEffect 在组件中创建由渲染本身引起的操作(如发送 Ajax 请求,更改 DOM 等),即非用户操作。
副作用函数随着依赖项的触发而执行。
清理副作用一般在组件卸载时执行
useEffect(() =>{
// 实现副作用逻辑
return ()=> {
// 清除副作用逻辑
}
}, [] )
import {forwardRef, useEffect, useImperativeHandle, useRef, useState} from "react";
function App() {
const [count, setCount] = useState(0)
const handleIncrement = ()=>setCount(count + 1)
const handleDecrement = ()=>setCount(count - 1)
useEffect(() => {
console.log('useEffect')
}, [count]);
return (
<>
<div style={{ padding: 10}}>
<button onClick={handleIncrement}>+</button>
<span> {count} </span>
<button onClick={handleDecrement}>-</button>
</div>
</>
);
}
export default App;