key
在HTML
结构中是看不到的,是React
内部用来进行性能优化时使用key
在当前列表中要唯一的字符串或者数值(String/Number
)- 如果列表中有像
id
这种的唯一值,就用id
来作为key
值 - 如果列表中没有像
id
这种的唯一值,就可以使用index
(下标)来作为key
值
function App() {
return (
<div className="App">
<ul>{songs.map(item => <li key = {item.id} > {item.name} </li>)}</ul>
</div>
)
}
<div className="App">
{flag ? 'react' : 'vue'}
{flag ? <span>this is span</span> : null}
</div>
- JSX必须有一个根节点,如果没有根节点,可以使用
<></>
(幽灵节点)替代 - 所有标签必须形成闭合,成对
闭合
或者自闭合
都可以 - JSX中的语法更加贴近JS语法,属性名采用驼峰命名法
class -> className
for -> htmlFor
- JSX支持多行(换行),如果需要换行,需使用
()
包裹,防止bug出现
- 拉取准备好的项目模块到本地 ,安装依赖,run起来项目
https://gitee.com/react-course-series/react-jsx-demo - 按照图示,完成
评论数据渲染
tab内容渲染
评论列表点赞和点踩
三个视图渲染
import './index.css'
import React from "react";
import avatar from './images/avatar.png'
// 依赖的数据
const state = {
// hot: 热度排序 time: 时间排序
tabs: [
{
id: 1,
name: '热度',
type: 'hot'
},
{
id: 2,
name: '时间',
type: 'time'
}
],
active: 'time',
list: [
{
id: 1,
author: '刘德华',
comment: '给我一杯忘情水',
time: new Date(),
// 1: 点赞 0:无态度 -1:踩
attitude: 1
},
{
id: 2,
author: '周杰伦',
comment: '哎哟,不错哦',
time: new Date(),
// 1: 点赞 0:无态度 -1:踩
attitude: 0
},
{
id: 3,
author: '五月天',
comment: '不打扰,是我的温柔',
time: new Date(),
// 1: 点赞 0:无态度 -1:踩
attitude: -1
}
]
}
const num_comments = state.list.length
function formatTime(time){
return `${time.getFullYear()}-${time.getMonth()+1}-${time.getDate()} ${" "} ${time.getHours()}:${time.getMinutes()}`
}
function App () {
return (
<div className="App">
<div className="comment-container">
{/* 评论数 */}
<div className="comment-head">
<span>{num_comments} 评论</span>
</div>
{/* 排序 */}
<div className="tabs-order">
<ul className="sort-container">
{state.tabs.map(item=><li key={item.id} className={item.type === state.active?'on':''}>按{item.name}排序</li>
)}
</ul>
</div>
{/* 添加评论 */}
<div className="comment-send">
<div className="user-face">
<img className="user-head" src={avatar} alt="" />
</div>
<div className="textarea-container">
<textarea
cols="80"
rows="5"
placeholder="发条友善的评论"
className="ipt-txt"
/>
<button className="comment-submit">发表评论</button>
</div>
<div className="comment-emoji">
<i className="face"></i>
<span className="text">表情</span>
</div>
</div>
{/* 评论列表 */}
<div className="comment-list">
{state.list.map( item => (
<div className="list-item">
<div className="user-face">
<img className="user-head" src={avatar} alt="" />
</div>
<div className="comment">
<div className="user">{item.author}</div>
<p className="text">{item.comment}</p>
<div className="info">
<span className="time" id='date'>
{formatTime(item.time)}
</span>
<span className={item.attitude===1?'like liked':'like'}>
<i className="icon" />
</span>
<span className={item.attitude===-1?'hate hated':'hate'}>
<i className="icon" />
</span>
<span className="reply btn-hover">
删除
</span>
</div>
</div>
</div>
))}
</div>
</div>
</div>
)
}
export default App
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
)
- 组件的名称 必须首字母大写 ,react内部会根据这个来判断是组件还是普通的HTML标签
- 函数组件 必须有返回值 ,表示该组件的 UI 结构;如果不需要渲染任何内容,则返回 null
- 组件就像 HTML 标签一样可以被渲染到页面中。组件表示的是一段结构内容,对于函数组件来说,渲染的内容是函数的 返回值 就是对应的内容
- 使用函数名称作为组件标签名称,可以成对出现也可以自闭合
function HelloFn () {
return <div>这是我的第一个函数组件!</div>
}
// 定义类组件
function App () {
return (
<div className="App">
{/* 渲染函数组件 */}
<HelloFn />
<HelloFn></HelloFn>
</div>
)
}
export default App
- 类名称也必须以大写字母开头
- 类组件应该继承
React.Component
父类,从而使用父类中提供的方法或属性 - 类组件必须提供
render
方法且 必须有返回值,表示该组件的 UI 结构
// 引入React
import React from 'react'
// 定义类组件
class HelloC extends React.Component {
render () {
return <div>这是我的第一个类组件!</div>
}
}
function App () {
return (
<div className="App">
{/* 渲染类组件 */}
<HelloC />
<HelloC></HelloC>
</div>
)
}
export default App
- 如何绑定事件
- 语法:on + 事件名称 = { 事件处理程序 } ,比如:
<div onClick={ onClick }></div>
- 采用驼峰命名法,比如onMouseEnter、onFocus
function HelloFn () {
// 定义事件回调函数
const clickHandler = () => {
console.log('事件被触发了')
}
return (
//绑定事件
<button onClick={clickHandler}>click me!</button>
)
}
////// 类组件
// 整体的套路都是一致的 和函数组件没有太多不同
// 唯一需要注意的 因为处于class 类环境下 所以定义事件回调函数 以及 绑定它写法上有不同
// 定义的时候: class Fields语法
// 使用的时候: 需要借助this关键词获取
// 注意事项: 之所以要采取class Fields写法是为了保证this的指向正确 永远指向当前的组件实例
import React from "react"
class CComponent extends React.Component {
// class Fields
clickHandler = (e, num) => {
// 这里的this指向的是正确的当前的组件实例对象
// 可以非常方便的通过this关键词拿到组件实例身上的其他属性或者方法
console.log(this)
}
clickHandler1 () {
// 这里的this 不指向当前的组件实例对象 undefined 存在this丢失问题
console.log(this)
}
render () {
return (<div>
<button onClick={(e) => this.clickHandler(e, '123')}>click me</button>
<button onClick={this.clickHandler1}>click me</button>
</div>
)}}
function App () {
return (<div><CComponent /></div>)
}
export default App
- 获取事件对象
function HelloFn() {
//事件回调
const clickHandler = (e,msg) => {
//关闭对象默认跳转, 点击百度不会跳转
e.preventDefault()
console.log('事件被触发了', msg)
}
return (<a href="https://www.baidu.com/" onClick={(e)=>clickHandler(e,'You can not leave.')}>百度</a>)
}
- 定义状态必须通过
state
实例属性的方法 提供一个对象 名称是固定的state
- 修改state中任何值,都必须走
setState
方法 - 类组件很少使用了,函数组件偏多
- 回调函数必须以
funcName = () =>{}
形式给出
import React from "react";
class TestComponent extends React.Component {
state={
//初始状态
name:'the Communist Party. ',
oath:' ',
count: 0
}
partyMemberOath=()=>{
//修改状态
//注意:不可直接修改状态值,需要调用setState方法
this.setState({
oath : 'Serve the people wholeheartedly!',
count: this.state.count+1
}
)
}
render() {
return (
<div>
//使用状态
<li>Party:{this.state.name}</li>
<li>The oath:{this.state.oath}</li>
//影响视图
<button onClick = {this.partyMemberOath}>Say {this.state.count}</button>
</div>
)
}
}
目标任务:
能够理解不可变的意义并且知道在实际开发中如何修改状态
概念:不要直接修改状态的值,而是基于当前状态创建新的状态值
...
state = {
count: 0,
list:[1,2,3],
person:{
name:'jack',
age:20
}
}
//列表添加、修改
this.setState({
count: this.state.count + 1
// 遍历原列表,在其基础上添加
list: [...this.state.list, 4, 5],
// 覆盖原来的属性 就可以达到修改对象中属性的目的
person: {
...this.state.person,
name: 'rose'
}
})
//列表删除 (删掉2)
this.setState({
list: this.state.list.filter(item=> item !== 2),
}
})
...
- 在组件的state中声明一个组件的
状态数据
- 将
状态数据
设置为input
标签元素的value
的值 - 为
input
添加change
事件,在事件处理程序中,通过事件对象e
获取到当前文本框的值 - 调用
setState
方法,将文本框内的值设为state
状态的最新值
class TestComponent extends React.Component {
state={
//初始状态
name:'the Communist Party. ',
oath:' ',
count:0,
content:'Initial Content'
}
partyMemberOath=()=>{
//修改状态
//注意:不可直接修改状态值,需要调用setState方法
this.setState({
oath : 'Serve the people wholeheartedly!',
count: this.state.count+1
}
)
}
showContent=(e)=>{
this.setState({
content : e.target.value
})
}
render() {
//使用状态
return (
//幽灵标签,防止两个并行div标签导致的报错
<>
<div>
<li>Party:{this.state.name}</li>
<li>The oath:{this.state.oath}</li>
<button onClick = {this.partyMemberOath}>{this.state.count} Say</button>
</div>
<div>
Content:{this.state.content}
</div>
<input type={"password"} value={this.state.content} onChange = {this.showContent}/>
</>
)
}
}
- 拉取项目模板到本地,安装依赖,run起来项目
https://gitee.com/react-course-series/react-component-demo - 完成tab点击切换激活状态交互
- 完成发表评论功能
注意:生成独立无二的id 可以使用 uuid 包yarn add uuid
- 为
<li><li/>
添加onClick
属性 - 编写组件
tabChange
- 完成事件绑定
onClick={()=>this.tabChange(item.type)
...
tabChange = (type) => {
this.setState({
active : type
})
}
...
...
{/* 排序 */}
<div className="tabs-order">
<ul className="sort-container">
{this.state.tabs.map(item=> <li
onClick={()=>this.tabChange(item.type)}
key={item.id}
className={item.type === this.state.active?'on':''}>
按{item.name}排序
</li>
)}
</ul>
</div>
...
- 为
state
添加输入框<textarea/>
内容变量comment
- 绑定
onChange
事件(可添加onClick
增加用户友好)实时更改state.comment
- 为
发表评论
添加<button/>
并绑定onClick
事件更新state.list
...
state = {
// hot: 热度排序 time: 时间排序
tabs: [
{
id: 1,
name: '热度',
type: 'hot'
},
{
id: 2,
name: '时间',
type: 'time'
}
],
active: 'time',
list: [
{
id: 1,
author: '刘德华',
comment: '给我一杯忘情水',
time: new Date(),
// 1: 点赞 0:无态度 -1:踩
attitude: 1
},
{
id: 2,
author: '周杰伦',
comment: '哎哟,不错哦',
time: new Date(),
// 1: 点赞 0:无态度 -1:踩
attitude: 0
},
{
id: 3,
author: '五月天',
comment: '不打扰,是我的温柔',
time: new Date(),
// 1: 点赞 0:无态度 -1:踩
attitude: -1
}
],
comment:'请输入内容',
ifClick: false
}
...
//提交组件
submitComment = () => {
this.setState({
list:[...this.state.list,{
id:this.state.list.length+1,
author:"周杰伦",
comment:this.state.comment,
time:new Date(),
attitude:0
}]
})
}
//输入框内容更改
contentChangeBegin = () => {
if(!this.state.ifClick){
this.setState({
comment: '',
ifClick: true
})
}
}
contentChangIng = (e) => {
this.setState({
comment:e.target.value
})
}
//添加评论
<>
<div className="comment-send">
<div className="user-face">
<img className="user-head" src={avatar} alt="" />
</div>
<div className="textarea-container">
{/*输入框*/}
<textarea
cols="80"
rows="5"
className="ipt-txt"
value={this.state.comment}
onClick={this.contentChangeBegin}
onChange={this.contentChangIng}
/>
<button
className = "comment-submit"
onClick={this.submitComment}
>
发表评论
</button>
</div>
<div className="comment-emoji">
<i className="face"></i>
<span className="text">表情</span>
</div>
</div>
</>
uuid:npm/yarn add uuid
包可独一无二生成id
- 引入
import {v4 as uuid} from 'uuid'
使用uuid()
import {v4 as uuid} from 'uuid'
...
list: [
{
id: uuid(),
author: '刘德华',
comment: '给我一杯忘情水',
time: new Date(),
// 1: 点赞 0:无态度 -1:踩
attitude: 1
},
{
id: uuid(),
author: '周杰伦',
comment: '哎哟,不错哦',
time: new Date(),
// 1: 点赞 0:无态度 -1:踩
attitude: 0
},
{
id: uuid(),
author: '五月天',
comment: '不打扰,是我的温柔',
time: new Date(),
// 1: 点赞 0:无态度 -1:踩
attitude: -1
}
],
...
submitComment = () => {
this.setState({
list:[...this.state.list,{
id:uuid(),
author:"周杰伦",
comment:this.state.comment,
time:new Date(),
attitude:0
}]
})
}
...
...
contentDelete = (id) => {
this.setState({
list:this.state.list.filter(item=>item.id !== id)
})
}
...
<span
onClick={()=>this.contentDelete(item.id)}
className="reply btn-hover">
删除
</span>
...
...
//改变list某个值 需要 map 遍历再筛选id再修改
toggleLike = (curItem) => {
const {attitude,id} = curItem
this.setState({
list:this.state.list.map(item => {
if(item.id===id){
return {
...item,
attitude: attitude=== 1?0:1
}
}
else{
return item
}
})
})
}
toggleHate = (curItem) => {
const {attitude,id} = curItem
this.setState({
list:this.state.list.map(item => {
if(item.id===id){
return {
...item,
attitude: attitude=== -1?0:-1
}
}
else{
return item
}
})
})
}
...
<span onClick = {() => this.toggleLike(item)}
className={item.attitude=== 1?'like liked':'like'}>
<i className="icon" />
</span>
<span onClick = {() => this.toggleHate(item)}
className={item.attitude=== -1?'like liked':'like'}>
<i className="icon" />
</span>
- 每个组件是个
独立封闭
的个体,只能使用自己的数据state
- 组件化开发(OOP开发)过程中,完整的功能会被拆分,不可避免的需要数据交互
- 组件间关系包括:
- 父组件提供要传递的数据 -
state
- 给子组件标签添加属性值为 state 中的数据
- 子组件中通过
props
接收父组件中传过来的数据- 类组件使用 this.props 获取 props 对象
- 函数组件通过参数获取 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
props
是只读对象(readOnly),子组件无法更改props
props
可以是传递任意数据:对象{this.state.message}
、函数{()=>{console.log(*)}}
、JSX{<span>***</span>}
口诀: 父组件给子组件传递回调函数,子组件调用
- 父组件提供一个
回调函数
-用于接收数据
- 将
回调函数
作为属性的值传递给子组件 - 子组件通过
props
调用回调函数
- 将子组件中的数据作为参数传递给
回调函数
import React from 'react'
// 子组件
function Son(props) {
function handleClick() {
// 调用父组件传递过来的回调函数 并注入参数
props.changeMsg('this is newMessage')
}
return (
<div>
{props.msg}
<button onClick={handleClick}>change</button>
</div>
)
}
class App extends React.Component {
state = {
message: 'this is message'
}
// 提供回调函数
changeMessage = (newMsg) => {
console.log('子组件传过来的数据:',newMsg)
this.setState({
message: newMsg
})
}
render() {
return (
<div>
<div>父组件</div>
<Son
msg={this.state.message}
// 传递给子组件
changeMsg={this.changeMessage}
/>
</div>
)
}
}
export default App
通过状态提升机制,利用共同的父组件实现兄弟通信
- 将
共享状态
提升到最近的公共父组件
中,由公共父组件
管理这个状态
{/* 接收数据的组件 */}
<SonA msg={this.state.message} />
{/* 修改数据的组件 */}
<SonB changeMsg={this.changeMsg} />
- 提供
共享状态
- 提供操作
共享状态
的方法
- 要接收数据状态的子组件通过
props
接收数据 - 要传递数据状态的子组件通过
props
接收方法,调用方法传递数据
import React from 'react'
// 子组件A
function SonA(props) {
return (
<div>
SonA
{props.msg}
</div>
)
}
// 子组件B
function SonB(props) {
return (
<div>
SonB
<button onClick={() => props.changeMsg('new message')}>changeMsg</button>
</div>
)
}
// 父组件
class App extends React.Component {
// 父组件提供状态数据
state = {
message: 'this is message'
}
// 父组件提供修改数据的方法
changeMsg = (newMsg) => {
this.setState({
message: newMsg
})
}
render() {
return (
<>
{/* 接收数据的组件 */}
<SonA msg={this.state.message} />
{/* 修改数据的组件 */}
<SonB changeMsg={this.changeMsg} />
</>
)
}
}
export default App
上图是一个react形成的嵌套组件树,如果我们想从App组件向任意一个下层组件传递数据,该怎么办呢?目前我们能采取的方式就是一层一层的props往下传,显然很繁琐 那么,Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法
- 创建
Context
对象 导出Provider
和Consumer
对象
const {Provider,Consumer} = createContext()
- 使用
Provider
包裹上层组件提供数据
<Provider value = {this.state.message}>
{/*根组件*/}
</Provider>
- 需要用到数据的 组件使用 Consumer 获取包裹的数据
<Consumer >
{value => /* 基于 context 值进行渲染*/}
</Consumer>