React 函数组件
React 事件
事件绑定方式
<button onclick="alert(123)">点我一下</button><button id="btn01">点我一下</button>document.getElementById('btn01').onclick = function(){};document.getElementById('btn01').addEventListener('click', function(){}, false);
在React中事件需要通过元素的属性来设置。
- 和原生JS不同,在React中事件的属性需要使用驼峰命名法:
onclick -> onClickonchange -> onChange
- 属性值不能直接执行代码,而是需要一个回调函数:
onclick="alert(123)" -> onClick={()=>{alert(123)}}
在React中,无法通过 return false 取消默认行为。
事件对象
事件对象
- React事件中同样会传递事件对象,可以在响应函数中定义参数来接收事件对象。
- React中的事件对象同样不是原生的事件对象,是经过React包装后的事件对象。
- 由于对象进行过包装,所以使用过程中我们无需再去考虑兼容性问题。
const App = () => {
const clickHandler = (event) => {
event.preventDefault(); // 取消默认行为
event.stopPropagation(); // 取消事件的冒泡
alert('我是App中的clickHandler!');
};
return <div
onClick={() => {
alert('div');
}}
style={{width: 200, height: 200, margin: '100px auto', backgroundColor:'#bfa'}}>
<button onClick={() => {
alert(123);
}}>点我一下
</button>
<button onClick={clickHandler}>哈哈</button>
<br/>
<a href="https://www.baidu.com" onClick={clickHandler}>超链接</a>
</div>
};
// 导出App
export default App;React Prop
Prop 简介
在 React 项目中,LogItem的数据是写死的。
如果将组件中的数据全部写死,将会导致组件无法动态设置,不具有使用价值。
我们希望组件数据可以由外部设置,在组件间,父组件可以通过props(属性)向子组件传递数据。
在函数组件中,属性就相当于是函数的参数,可以通过参数来访问。
可以在函数组件的形参中定义一个props,props指向的是一个对象。
它包含了父组件中传递的所有参数。
props 是只读的,不能修改。
Prop 应用项目
src/App.js
import Logs from "./Components/Logs/Logs";
// 将组件粘合起来
const App = () => {
return <div>
<Logs date={new Date(2021, 7, 20, 19, 0)} LogDesc='React' LogTime='3天' />
<Logs date={new Date(2021, 8, 20, 19, 0)} LogDesc='React1' LogTime='31天' />
</div>
}
// 导出App
export default App;src/Components/Logs/Logs.js
import LogItem from "./LogItem/LogItem"
import './Logs.css'
// 日志容器
const Logs = (props) => {
return <div className="logs">
{/* 在父组件中为子组件设置属性 */}
<LogItem date={props.date} LogDesc={props.LogDesc} LogTime={props.LogTime} />
</div>
}
export default Logssrc/Components/Logs/LogItem/LogItem.js
import React from 'react'
import MyDate from './MyDate/MyDate'
import './LogItem.css'
const LogItem = (props) => {
// 在函数组件中,属性相当于函数的参数,可通过参数接受
return (
<div className='item'>
<MyDate date={props.date}></MyDate>
{/* 内容容器 */}
<div className='content'>
<h2 className='desc'>{props.LogDesc}</h2>
<div className='time'>{props.LogTime}</div>
</div>
</div>
)
}
export default LogItemsrc/Components/Logs/LogItem/MyDate/MyDate.js
import React from 'react'
import './MyDate.css'
const MyDate = (props) => {
// 获取月份
const month = props.date.toLocaleString('zh-CN', { month: 'long' });
// 获取日期
const date = props.date.getDate();
return (
<div>
{/* 日期容器 */}
<div className='date'>
<div className='month'>{month}</div>
<div className='day'>{date}</div>
</div>
</div>
)
}
export default MyDate整理项目目录
因 src/App.js 是入口文件,故应将其进行简化,同时对日志项的增加进行修改。
src/App.js
import Logs from "./Components/Logs/Logs";
const App = () => {
return <div>
<Logs/>
</div>
};
// 导出App
export default App;src/Components/Logs/Logs.js
/* 日志的容器 */
import LogItem from "./LogItem/LogItem";
import './Logs.css';
const Logs = () => {
// 模拟一组从服务器中加载的数据
const logsData = [
{
id: '001',
date: new Date(2021, 1, 20, 18, 30),
desc: '学习九阳神功',
time: 30
},
{
id: '002',
date: new Date(2022, 2, 10, 12, 30),
desc: '学习降龙十八掌',
time: 20
},
{
id: '003',
date: new Date(2022, 2, 11, 11, 30),
desc: '学习JavaScript',
time: 40
},
{
id: '004',
date: new Date(2022, 2, 15, 10, 30),
desc: '学习React',
time: 80
}
];
// 循环遍历数据,生成对应的item
const logItemDate = logsData.map(itme => <LogItem key={itme.id} date={itme.date} LogDesc={itme.desc} LogTime={itme.time}></LogItem>)
return <div className="logs">
{
logItemDate
// logsData.map(item => <LogItem {...item}/> )
}
</div>
};
export default Logs;React 状态 State
引出问题
以加减计数器为例。
src/App.js
import "./App.css";
const App = () => {
let counter = 1;
const addHandler = () => {
counter++;
};
const lessHandler = () => {
counter--;
};
return (
<div className="app">
<h1>{counter}</h1>
<button onClick={addHandler}>+</button>
<button onClick={lessHandler}>-</button>
</div>
);
};
// 导出App
export default App;src/App.css
.app {
width: 300px;
height: 300px;
margin: 50px auto;
background-color: #bfa;
text-align: center;
}
.app button {
width: 100px;
font-size: 50px;
margin: 0 20px;
}此时在页面中点击按钮时,counter 的数据发生了改变,但是页面显示仍是初始值。
状态 State
在 React 中,当组件渲染完毕后,再修改组件中的变量,不会使组件重新渲染。要使得组件可以收到变量的影响,必须在变量修改后对组件进行重新渲染。这里我们就需要一个特殊变量,当这个变量被修改使,组件会自动重新渲染。
state 相当于一个变量,只是这个变量在 React 中进行了注册。React 会监控这个变量的变化,当 state 发生变化时,会自动触发组件的重新渲染,使得我们的修改可以在页面中呈现出来。
在函数组件中,我们需要通过钩子函数,获取 state,使用钩子 useState() 来创建 state。
它需要一个值作为参数,这个值就是 state 的初始值。
- 该函数会返回一个数组。
- 数组中第一个元素,是初始值。
- 初始值只用来显示数据,直接修改不会触发组件的重新渲染。
- 数组中的第二个元素,是一个函数,通常会命名为 setXxx。
- 这个函数用来修改 state,调用其修改 state 后会触发组件的重新渲染,并且使用函数中的值作为新的 state 值。
- 数组中第一个元素,是初始值。
src/App.js
import "./App.css";
import { useState } from "react";
const App = () => {
const [counter, setCounter] = useState(1);
const addHandler = () => {
setCounter(counter + 1);
};
const lessHandler = () => {
setCounter(counter - 1);
};
return (
<div className="app">
<h1>{counter}</h1>
<button onClick={addHandler}>+</button>
<button onClick={lessHandler}>-</button>
</div>
);
};
// 导出App
export default App;State 注意
- state实际就是一个被React管理的变量,当通过 setState() 修改变量的值时,会触发组件的自动重新渲染。
- 只有state值发生变化时,组件才会重新渲染。
- 当state的值是对象时,会使用新的对象替换旧的对象,
const [user, setUser] = useState({name: 's', age: '19'})->setUser({name: 'ww'}), 修改后的user会只有name属性。正确修改:setUser({...user, name: 'ww'})。 - 当通过 setState() 去修改一个state时,并不表示修改当前的state,它修改的是组件下一次渲染时state值。
- setState() 会触发组件的重新渲染,它是异步的,当调用 setState() 需要用到旧的state的值时,要注意;有可能会出现计算错误的情况;为了避免,可通过为 setState() 传递回调函数的形式修改state。
此时情况仍然存在。
const addHandler = () => {
// setCounter(counter + 1)
setTimeout(()=>{
setCounter(()=>{
// setState 中回调函数的返回值,会成为新的state
return counter + 1
})
},1000)
}setState 中回调函数执行时,React会将最新的state作为参数传递。
const addHandler = () => {
// setCounter(counter + 1)
setTimeout(()=>{
// setCounter((prevCounter)=>{
// // setState 中回调函数的返回值,会成为新的state
// // 回调函数执行时,React会将最新的state作为参数传递
// return prevCounter + 1
// })
setCounter(prevCounter => prevCounter + 1)
},1000)
}React Ref dom
获取原生的DOM对象
- 可以使用传统的document来对DOM进行操作。
- 直接从React处获取DOM对象。
步骤:
1. 创建一个存储DOM对象的容器;
- 使用 **useRef()** 钩子函数;
- 钩子函数的注意事项:;
- React中的钩子函数只能用于函数组件或自定义钩子;
- 钩子函数只能直接在函数组件中调用;
2. 将容器设置为想要获取DOM对象元素的ref属性 `<h1 ref={xxx}>....</h1>`;
- React会自动将当前元素的DOM对象,设置为容器current属性;
import './App.css';
import { useRef, useState } from "react";
const App = () => {
console.log('组件被渲染了');
// 创建一个容器
const h1Ref = useRef();
const CliclH = () => {
h1Ref.current.innerHTML = '111111'
console.log(h1Ref.current);
}
return <div className='app' ref={h1Ref}>
<h1>222222222</h1>
<button onClick={CliclH}>1</button>
<button>2</button>
</div>
};
// 导出App
export default App;useRef()
- 返回的就是一个普通的JS对象
{current:undefined}。 - 所以我们直接创建一个js对象,也可以代替 useRef() 。
- 区别:
- 我们创建的对象,组件每次重新渲染都会创建一个新对象;
- useRef() 创建的对象,可以确保每次渲染获取到的都是同一个对象;
- 当你需要一个对象不会因为组件的重新渲染而改变时,就可以使用useRef()。