文章目录
react学习
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Hello React</title>
<!-- 加载 React。-->
<!-- 注意: 部署时,将 "development.js" 替换为 "production.min.js"。-->
<script type="text/javascript" src="https://unpkg.com/react@16/umd/react.development.js"></script>
<!-- 用于之覅react操作dom -->
<script type="text/javascript" src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转换为js -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="app"></div>
<div id="demo"></div>
<!-- type="text/babel" 编写jsx -->
<script type="text/babel">
// 1.创建虚拟DOM
const VDOM = (
<h1 id="titile">
<span>HELLO,REACT</span>
</h1>
)
console.log(typeof VDOM);
console.log(VDOM instanceof Object);
//2.渲染虚拟DOM到页面
// ReactDOM.render(虚拟Dom,容器)
ReactDOM.render(VDOM, document.getElementById('app'))
// 真实DOM
const TDOM = document.getElementById('demo')
console.log('虚拟DOM:', VDOM );
console.log('真实DOM:', TDOM);
/*
关于虚拟DOM:
本质是Object类型的对象(一般对象)
虚拟DOM比较‘轻’,真实DOM比较‘重’
虚拟DOM最终会被React转化为真实DOM,呈现在页面上
*/
debugger
</script>
</body>
</html>
开发者插件
React Developer Tools
//谷歌应用商店下载
组件(两种创建组件方式)
函数式组件
//函数式组件
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Hello React</title>
<!-- 加载 React。-->
<!-- 注意: 部署时,将 'development.js' 替换为 'production.min.js'。-->
<script type='text/javascript' src='https://unpkg.com/react@16/umd/react.development.js'></script>
<!-- 用于react操作dom -->
<script type='text/javascript' src='https://unpkg.com/react-dom@16/umd/react-dom.development.js'></script>
<!-- 引入babel,用于将jsx转换为js -->
<script src='https://unpkg.com/babel-standalone@6/babel.min.js'></script>
</head>
<body>
<div id='app'></div>
<script type='text/babel'>
console.log(this);
// 1.创建函数式组件
function MyComponent() {
console.log(this);//此处的this是undefined 因为babel编译后开启了严格模式
return <h2>我是函数定义的组件(适用于【简单组件】)</h2>
}
// 2.渲染组件到页面
ReactDOM.render(<MyComponent/>,document.getElementById('app'))
/*
执行了ReactDOM,.render(<MyComponent/>......之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实的DOM,随后呈现在页面中。
*/
</script>
</body>
</html>
类式组件
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Hello React</title>
<!-- 加载 React。-->
<!-- 注意: 部署时,将 'development.js' 替换为 'production.min.js'。-->
<script type='text/javascript' src='https://unpkg.com/react@16/umd/react.development.js'></script>
<!-- 用于react操作dom -->
<script type='text/javascript' src='https://unpkg.com/react-dom@16/umd/react-dom.development.js'></script>
<!-- 引入babel,用于将jsx转换为js -->
<script src='https://unpkg.com/babel-standalone@6/babel.min.js'></script>
</head>
<body>
<div id='app'></div>
<script type='text/babel'>
//1.创建类式组件 需要继承React中的一个类
class MyComponent extends React.Component {
// render是放在哪里的?--MyComponent(类)的原型对象上,供实例使用。
//render中的this是谁?--MyComponent的实例对象 <=> MyComponent组件实例对象。
render() {
console.log('render中的this:',this);
return (
<h1>我是用类定义的组件【适用于复杂组件的定义】</h1>
)
}
}
// 2.渲染组件到页面
ReactDOM.render(<MyComponent />, document.getElementById('app'))
/*
执行了ReacatDOM.render(<MyComponent/)......之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用类定义得,随后new出来该类的实例,并通过实例调用到原型上的render方法。
3.将render返回的虚拟DOM转化为真实DOM,随后呈现在页面中。
*/
</script>
</body>
</html>
安装
//直接安装
npx create-react-app my-pro1
//全局安装
npm i -g create-react-app //安装脚手架
create-react-app my-pro1 //创建项目
使用
1. jsx语法规则
//JSX就是Javascript和XML结合的一种格式。React发明了JSX,可以方便的利用HTML语法来创建虚拟DOM,当遇到<,JSX就当作HTML解析,遇到{就当JavaScript解析.
1.定义虚拟DOM,不要写引号
2.标签中混入JS表达式时要用{}
3.样式的类名指定不要用class,要用className
4.内联样式,要用style={{key:value}}的形式去写,即style= {{color:'yellow'}}
5.必须有且仅有一个根标签 没有根节点可以使用<></> (幽灵节点,页面不显示元素)替代
6.标签必须闭合
7.标签首字母
(1).若小写字母开头,转为html中同名元素,若无则报错
(2).若大写字母开头,react就去渲染对应的组件,若没有
//示例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Hello React</title>
<!-- 加载 React。-->
<!-- 注意: 部署时,将 "development.js" 替换为 "production.min.js"。-->
<script type="text/javascript" src="https://unpkg.com/react@16/umd/react.development.js"></script>
<!-- 用于之覅react操作dom -->
<script type="text/javascript" src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转换为js -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<style>
.title {
background-color: salmon;
}
</style>
<body>
<!-- 准备好一个“容器” -->
<div id="app"></div>
<!-- type="text/babel" 编写jsx -->
<script type="text/babel">
const myId = "title"
const myData = "Hellow,React"
// 1.创建虚拟dom
const VDOM = (
<div>
<h1 className="title" id={myId.toLowerCase()}>
<span style={{ color: 'yellow', fontSize: '20px' }}>{myData.toLowerCase()}</span>
</h1>
<h1 className="title" id={myId.toUpperCase()}>
<span style={{ color: 'yellow', fontSize: '20px' }}>{myData.toLowerCase()}</span>
</h1>
<Good>123</Good>
</div>
)//虚拟DOM
//2.渲染虚拟DOM到页面
// ReactDOM.render(虚拟Dom,容器)
ReactDOM.render(VDOM, document.getElementById('app'))
/*
jsx语法规则:
1.定义虚拟DOM,不要写引号
2.标签中混入JS表达式时要用{}
3.样式的类名指定不要用class,要用className
4.内联样式,要用style={{key:value}}的形式去写,即style= {{color:'yellow'}}
5.虚拟DOM只能有一个根标签
6.标签必须闭合
7.标签首字母
(1)..若小写字母开头,转为html中同名元素,若无则报错
(2).若大写字母开头,react就去渲染对应的组件,若没有
*/
</script>
</body>
</html>
2.状态(数据)修改
// 1.创建类式组件
class Weather extends React.Component {
state = { isHot: false, wind: '微风' }
render() {
const { isHot, wind } = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}
changeWeather = () => {
const { isHot } = this.state
//使用setState修改状态(数据)
this.setState((state, props) => {
return {
isHot: !isHot
}
})
}
}
//2.渲染虚拟DOM到页面
ReactDOM.render(<Weather />, document.getElementById('app'))
3. props
<script src="https://unpkg.com/prop-types@15.6/prop-types.js"></script>
// 1.创建虚拟dom
class Person extends React.Component {
//组件标签限制属性类型
static propTypes = {
name: PropTypes.string.isRequired,//限制属性类型 isRequired代表必传
sex: PropTypes.string,
age: PropTypes.number,
}
// 指定默认标签属性的默认值
static defaultProps = {
sex: "阴阳人",
age: 20
}
render() {
// console.log(this);
const { name, age, sex } = this.props //获取props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
)
}
}
//2.渲染虚拟DOM到页面
const p = { name: "老刘" }
// console.log('@', ...p);
//react中可以通过扩展语法,{}分割 注意:与js中的{}不是一个意思
ReactDOM.render(<Person {...p} />, document.getElementById('app'))
使用方法
1. jsx
1.jsx列表渲染
//列表渲染
//注意:遍历列表需要一个number/string类型的key 提高性能
const songs = [
{
id: 1, name: "1~"
},
{
id: 2, name: "2~"
},
{
id: 3, name: "3~"
},
{
id: 4, name: "4~"
}
]
function App() {
return (
<div className="App">
<ul>
{songs.map(song => <li key={song.id}>{song.name}</li>)}
</ul>
</div>
);
}
export default App;
2.jsx条件渲染
//条件渲染
const flag=true
function App() {
return (
<div className="App">
<ul>
{flag ? '1' : '2'}
</ul>
</div>
);
}
export default App;
----------------------------------------------------------------------------------
// 有一个状态type 1 2 3
//1 h1
//2 h2
//3 h3
// 原则:模板中的逻辑尽量保持精简
// 复杂的多分枝的逻辑 收敛为一个函数 模板只负责调用函数
const getHtag = (type) => {
if (type === 1) return <h1>h1标签</h1>
if (type === 2) return <h2>h2标签</h2>
if (type === 3) return <h3>h3标签</h3>
}
function App() {
return (
<div className="App">
{getHtag(3)}
</div>
);
}
export default App;
3.jsx样式控制
//1.行内样式- 元素身上绑定一个style属性即可
//2.类名样式-在元素身上绑定一个className属性即可
import './App.css'
const getHtag = (type) => {
if (type === 1) return <h1>h1标签</h1>
if (type === 2) return <h2>h2标签</h2>
if (type === 3) return <h3>h3标签</h3>
}
const style = {
color: 'red',
fontSize: '24px'
}
// 动态控制这个active类名 满足条件渲染
const activeFlag = true
function App() {
return (
<div className={activeFlag?'active':''} style={style}>
{getHtag(3)}
</div>
);
}
export default App;
2. 组件
1.函数组件
// 定义函数组件
function Hello (){
return <div>这是函数组件</div>
}
function App(){
return (
<div className="App">
<Hello />
</div>
)
}
export default App
2.class类组件
import React from 'react'
// 定义函数组件
class Hello extends React.Component{
render(){
return <div>这是类组件</div>
}
}
function App(){
return (
<div className="App">
<Hello />
</div>
)
}
export default App
3. 事件绑定与参数传递
import React from 'react'
//1.传递一个额外参数 ()=>this.clickHandler('自定义参数')
//2.既需要e也需要额外参数 (e)=>this.clickHandler(e,'自定义参数')
// 定义函数组件
class Hello extends React.Component {
clickHandler = (e) => {
console.log(e);
alert('函数事件被触发了',e)
}
render() {
return <div onClick={this.clickHandler}>这是类组件</div>
}
}
function App() {
return (
<div className="App">
<Hello />
</div>
)
}
export default App
4. 表单组件
1.受控表单(类似vue中的v-model双向绑定)
import React from "react"
// 定义组件
class Counter extends React.Component {
state = {
message:"message",
}
inputChange = (e) => {
console.log('change事件触发了',e,this.state.message);
// 只能使用setState方法修改数据
this.setState({
message:e.target.value //将input value值交给state中的message
})
}
render() {
return (
<div>
<input type='text'
onChange={this.inputChange}
value={this.state.message}></input>
</div>
)
}
}
function App() {
return (
<div>
{
<Counter />
}
</div>
)
}
export default App
2. 非受控表单(操作dom 类似vue中的ref)
import React, { createRef } from "react"
// 定义组件
class Counter extends React.Component {
//msgRef可以自定义名称
msgRef = createRef() //creatRef 方法
getValue = (e) => {
console.log(this.msgRef.current.value); //获取当前input元素的value值
}
render() {
return (
<div>
<input type='text'
ref={this.msgRef} //通过ref获取元素
></input>
<button onClick={this.getValue}>获取input值</button>
</div>
)
}
}
function App() {
return (
<div>
{
<Counter />
}
</div>
)
}
export default App
5. 综合案例
import './index.css'
import avatar from './images/avatar.png'
import React from 'react'
import { v4 as uuid } from 'uuid'
// 时间格式化
function formatDate(time) {
return `${time.getFullYear()}-${time.getMonth()}-${time.getDate()}`
}
class App extends React.Component {
state = {
// hot: 热度排序 time: 时间排序
tabs: [
{
id: 1,
name: '热度',
type: 'hot'
},
{
id: 2,
name: '时间',
type: 'time'
}
],
active: 'hot',
list: [
{
id: 1,
author: '刘德华',
comment: '给我一杯忘情水',
time: new Date('2021-10-10 09:09:00'),
// 1: 点赞 0:无态度 -1:踩
attitude: 1
},
{
id: 2,
author: '周杰伦',
comment: '哎哟,不错哦',
time: new Date('2021-10-11 09:09:00'),
// 1: 点赞 0:无态度 -1:踩
attitude: 0
},
{
id: 3,
author: '五月天',
comment: '不打扰,是我的温柔',
time: new Date('2021-10-11 10:09:00'),
// 1: 点赞 0:无态度 -1:踩
attitude: -1
}
],
comment: '请输入内容', //评论框中的类容
}
tabChange = (type) => {
console.log(type);
this.setState({
active: type
})
}
// 受控组件回调
changeComment = (e) => {
this.setState({
comment: e.target.value
})
}
// 发表评价
submitComment = () => {
// 在state.list后面添加一项新的
this.setState({
list: [...this.state.list,
{
id: uuid(),//使用uuid确保id的唯一性
author: '张学友',
comment: this.state.comment,
time: new Date('2021-10-10 09:09:00'),
// 1: 点赞 0:无态度 -1:踩
attitude: 1
},
]
})
}
deleteComment = (id) => {
console.log(id);
this.setState({
list: this.state.list.filter(item => item.id !== id)
})
}
toogleLike = (currtItem) => {
console.log(currtItem);
const { id, attitude } = currtItem
this.setState({
list: this.state.list.map(item => {
// 如果id相同 把item的attitude的属性修改一下
//否则原样返回
if (item.id === id) {
return {
...item,
attitude: attitude === 1 ? 0 : 1
}
}
return item
})
})
}
toogleHate = (currtItem) => {
console.log(currtItem);
const { id, attitude } = currtItem
this.setState({
list: this.state.list.map(item => {
if (item.id === id) {
return {
...item,
attitude: attitude === -1 ? 0 : -1
}
}
return item
})
})
}
render() {
return (
<div className="App">
<div className="comment-container">
{/* 评论数 */}
<div className="comment-head">
<span>5 评论</span>
</div>
{/* 排序 */}
<div className="tabs-order">
<ul className="sort-container">
{
this.state.tabs.map(tab => (
<li
onClick={() => this.tabChange(tab.type)}
key={tab.id}
className={tab.type === this.state.active ? 'on' : ''}
>按{tab.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"
onChange={this.changeComment}
value={this.state.comment}
placeholder="发条友善的评论"
className="ipt-txt"
/>
<button onClick={this.submitComment} className="comment-submit">发表评论</button>
</div>
<div className="comment-emoji">
<i className="face"></i>
<span className="text">表情</span>
</div>
</div>
{/* 评论列表 */}
<div className="comment-list">
{
this.state.list.map(item => (
<div className="list-item" key={item.id}>
<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">{formatDate(item.time)}</span>
<span onClick={() => this.toogleLike(item)} className={item.attitude === 1 ? 'like liked' : 'like'}>
<i className="icon" />
</span>
<span onClick={() => this.toogleHate(item)} className={item.attitude === -1 ? 'hate hated' : 'hate'}>
<i className="icon" />
</span>
<span onClick={() => this.deleteComment(item.id)} className="reply btn-hover">删除</span>
</div>
</div>
</div>
))
}
</div>
</div>
</div>)
}
}
export default App
6. 组件通信
1.父子通信
①.方法
import React from 'react'
//父传子 props 函数
// 子传父: 子组件调用父组件传递过来的函数,将参数当成函数的实参传递给函数
// 子组件 Son
//函数子组件 SonA
function SonA(props) {
//通过传入的props获取父组件上所有的数据
const { msg, userInfo, getMsg ,template} = props
console.log(props);
return (
<div>我是函数子组件
<div>
{msg.map(item => <p key={item}>{item}</p>)}
</div>
<div>
{userInfo.name}
{userInfo.age}
</div>
{/* 子组件向父组件传递数据 */}
<button onClick={() => getMsg('这里是来自子组件中的数据')}>触发父组件传入的函数,传递参数</button>
<div>我是模板{template}</div>
</div>
)
}
//类子组件SonB
class SonB extends React.Component {
//通过this获取 props名称是固定的
render() {
const { msg, userInfo, getMsg, template } = this.props
getMsg()
return <div>我是类子组件
<div>
{msg.map(item => <p key={item}>{item}</p>)}
</div>
<div>
{userInfo.name}
{userInfo.age}
</div>
{/* 子组件向父组件传递数据 */}
<button onClick={() => getMsg('这里是来自子组件中的数据')}>触发父组件传入的函数</button>
<div>我是模板{template}</div>
</div>
}
}
// 父组件App
class App extends React.Component {
state = {
list: [
1, 2, 3
],
userInfo: {
name: "张三",
age: 18
},
template: <span> 父组件中的模板 jsx</span>
}
//接收子组件传递的参数
getMsg = (sonMsg) => {
console.log('父组件中的函数执行',sonMsg);
}
render() {
return (
<div>
{/* 子组件身上绑定属性 属性名可以自定义 保持语义化 */}
<SonA msg={this.state.list} userInfo={this.state.userInfo} getMsg={this.getMsg} template={this.state.template}></SonA>
<SonB msg={this.state.list} userInfo={this.state.userInfo} getMsg={this.getMsg} template={this.state.template}></SonB>
</div>
)
}
}
/*
1.props 是只读对象(readonly)
单项数据流 子组件只能读取,不能进行修改
2.props可以传递任意数据
数字 字符串 布尔值 数组 对象 函数 jsx
*/
export default App
②.案例
import React, { createContext } from "react"
// App->A->C
// App数据->C
/*
注意事项:
1.上层组件和下层组件关系是相对的 只要存在就可以使用 通常我们都会通过App作为数据提供方
2.这里涉及到的语法都是固定的 有两处 提供的位置 vlaue提供数据 获取的位置{value=>{value}}
*/
// 使用步骤:
// 1.导入createContext方法并执行,解构提供者与消费者
// 子组件 渲染列表
function ListItem(props) {
const { item, delItem } = props
return (
<div>
<div >
<h3>{item.name}</h3>
<p>{item.price}</p>
<p>{item.info}</p>
{/* 传递要删除子项的id */}
<button onClick={() => delItem(item.id)}>删除</button>
</div>
</div>
)
}
// 父组件 数据提供者
// 先不抽离组件 完成基础渲染后再去抽离
class App extends React.Component {
state = {
list: [
{ id: 1, name: '超级好吃的棒棒糖', price: 18.8, info: "开业大酬宾" },
{ id: 2, name: '超级好吃的大鸡腿', price: 28.8, info: "开业大酬宾" },
{ id: 3, name: '超级好吃的冰淇淋', price: 3.8, info: "开业大酬宾" },
]
}
// 给子组件传递的函数
delItem = (id) => {
console.log(id);
this.setState({
// 删除数据 filter
list: this.state.list.filter(item => {
return item.id !== id
})
})
}
render() {
return (
<div>
{this.state.list.map(item => <ListItem key={item.id} item={item} delItem={this.delItem} />
)}
</div>
)
}
}
export default App
2.兄弟通信 自定义事件模式产生技术方法evenetBus/通过共同的父组件通信
// 目标: B组件中的数据传给A
// 技术方案:
// 1.先把B中的数据通过子传父 传给App
// 2.再把App接收的Son中的数据 通过父传子 传给A
import React from "react"
function SonA(props) {
const { sendMsg, getMsg } = props
return (
<div>子组件A,
{sendMsg}
</div>
)
}
function SonB(props) {
const bMsg = "这是来自B组件中的数据"
const { getMsg } = props
function sendMsg() {
getMsg(bMsg)
}
return (
<div>子组件B
<button onClick={sendMsg}>发送数据</button>
</div>
)
}
// 父组件
class App extends React.Component {
state = {
sendMsg: ''
}
// 声明一个传给B组件的方法
getMsg = (msg) => {
console.log(msg);
// 把msg数据交给sendMsg
this.setState({
sendMsg: msg
})
}
render() {
return (
<div>
<SonA sendMsg={this.state.sendMsg} />
<SonB getMsg={this.getMsg} />
</div>
)
}
}
export default App
3.其他通信 mobx/redux/基于hook的方案
4.跨组件通信
import React, { createContext } from "react"
// App->A->C
// App数据->C
/*
注意事项:
1.上层组件和下层组件关系是相对的 只要存在就可以使用 通常我们都会通过App作为数据提供方
2.这里涉及到的语法都是固定的 有两处 提供的位置 vlaue提供数据 获取的位置{value=>{value}}
*/
// 使用步骤:
// 1.导入createContext方法并执行,解构提供者与消费者 类似vue中的provide,inject
const { Provider, Consumer } = createContext()
// 子组件A
function ComA() {
return (
<div>
ComA
<ComC />
</div>
)
}
// 孙组件C
function ComC() {
return (
<div>
ComC,获取爷组件数据:
{/* 使用Consumer 获取value */}
<Consumer>
{value=><span>{value}</span>}
</Consumer>
</div>
)
}
// 父组件
class App extends React.Component {
state = {
msg: "this is message"
}
render() {
return (
// 2.使用Provider包裹根组件 提供数据
<Provider value={this.state.msg}>
<div>
<ComA />
</div>
</Provider>
)
}
}
export default App
//hooks使用方式:
import React, { useState, createContext, useContext } from "react"
// App->A->C
// App数据->C
//const { Provider, Consumer } = createContext()
const Context = createContext() //Context身上有提供者Provider与消费者Consumer
// 子组件A
function ComA() {
const count = useContext(Context)//使用useContext获取Context身上的数据
return (
<div>
ComA
<br />
app传过来的数据为:{count}
<ComC />
</div>
)
}
// 孙组件C
function ComC() {
const count = useContext(Context)
return (
<div>
ComC,获取爷组件数据:
<br />
{count}
</div>
)
}
// 父组件
function App() {
const [count, setCount] = useState(0)
return (
// 2.使用Provider包裹根组件 提供数据
<Context.Provider value={count}>
<div>
<ComA />
<button onClick={()=>{setCount(count+1)}}>count+1</button>
</div>
</Context.Provider>
)
}
export default App
7. children 类似vue中的插槽
//children 可以传递jsx 函数 文本 普通标签元素等
import React from "react"
// 渲染列表
function ListItem(props) {
console.log(props);
const { children } = props
return (
<div>
{/* {children.map(child => child)} */}
{children}
</div>
)
}
class App extends React.Component {
render() {
return (
<div>
<ListItem>
<div>{'可以传jsx'}</div>
<p>{'p标签'}</p>
</ListItem>
</div>
)
}
}
export default App
8. props校验
1.安装属性校验包 yarn add prop-types
2.导入prop-types包
3.使用 组件名.propTypes={}给组件添加校验规则
import React from "react"
import PropTypes from 'prop-types'
function Test({ list }) {
// const { list } = props
return (
<div>
{list.map(item => <p>{item}</p>)}
</div>
)
}
Test.propTypes = {
// 定义各种规则
list: PropTypes.array //限定这里的list参数类型必须是数组类型
}
/*
1.常见类型: 数组 布尔值 字符串 函数 对象 symbol
2.React元素类型:element
3.必填项 isRequired 如:PropTypes.array.isRequired
4.特定的结构对象: shape({})
*/
class App extends React.Component {
render() {
return (
<div>
<Test list={[1,2,3]} />
</div>
)
}
}
export default App
9. props默认值
//默认值
import React from "react"
import PropTypes from 'prop-types'
function Test(props) {
const {
list,
pageSize = 10 //默认参数传递 组件内部才有这个prop
} = props
console.log(props)
return (
<div>
{pageSize}
</div>
)
}
Test.propTypes = {
// 定义各种规则
list: PropTypes.array.isRequired //限定这里的list参数类型必须是数组类型
}
class App extends React.Component {
render() {
return (
<div>
<Test list={[1, 2, 3]} />
</div>
)
}
}
export default App
10.生命周期
挂载阶段(按顺序执行一次)
constructor
render 渲染UI
componetDidMount 1.发送网络请求 2.dom操作
更新阶段(组件更新就会执行)
render 渲染UI
componentDidUpdate 注意:不要在里面使用setState
卸载阶段(组件被销毁时执行一次)
componentWillUnmount 消除操作 如定时器的销毁
注意:不可以在render/componnetDidUpdate中执行setState(防止循环渲染)
import React from "react"
class Test extends React.Component {
// 如果数据是组件的状态需要去影响视图 定义到state中
// 如果需要的数据状态 不需要影响视图 直接定义一个普通实例属性就可以了
// state中尽量保持精简
timer = null
componentDidMount() {
this.timer = setInterval(() => {
console.log('定时器开启');
}, 1000);
}
componentWillUnmount() {
console.log('componentWillUnmount');
// 清除定时器
clearInterval(this.timer)
}
render() {
return (
<div>Test</div>
)
}
}
class App extends React.Component {
constructor() {
super()
console.log('constructor');
}
state = {
count: 0,
flag: true
}
changeCount = () => {
this.setState({
count: this.state.count + 1
})
}
delTest = () => {
this.setState({
flag: !this.state.flag
})
}
// 组件挂载完毕时候 类似vue中的mounted 1.发送网络请求 2.dom操作
componentDidMount() {
console.log('componentDidMount');
}
// 组件更新时执行 注意:不要在里面使用setState
componentDidUpdate() {
console.log('componentDidUpdate');
}
// 渲染UI
render() {
console.log('render');
return (
<div>
this is div
{
this.state.flag ? <Test /> : ''
}
<button onClick={this.changeCount}>{this.state.count}click</button>
{/* 修改数据状态 达到销毁重建Test组件 */}
<button onClick={this.delTest}>修改flag 销毁或重建Test组件</button>
</div>
)
}
}
export default App
11. hooks(只能在函数组件中使用)
1.useState(数据与更新数据)
//使用步骤:
//1.导入useState函数
//2.执行这个函数并且传入初始值 必须在函数组件中
//3.[数据,修改数据的方法]
//4.使用数据 修改数据
//状态的读取和修改
//1.useState传过来的参数 作为count的初始值
//2.[count,setCount]这里的写法是一个解构赋值 useState返回值是一个数组
// 名字可以自定义吗?-》可以自定义保持语义化
// 顺序可以换吗?-》不可以 第一个参数就是数据状态 第二个参数就是修改数据的方法
//3.setCount函数 作用:用来修改count 依旧保持不能直接修改原值 生成一个新值替换
// setCount(基于原值计算得到的新值)
//4.count和setCount是一对的 绑在一起的 setCount只能修改对应的count的值
import {useDate} from 'react' //1.导入useState函数
function App(){
const {count,setCount}=useState(0) //3.[数据,修改数据的方法] //2.执行这个函数并且传入初始值 必须在函数组件中
return(
<div>
<button oncClick={()=>setCount(count+1)}>{count}</button> {/* 4.使用数据 修改数据 */}
</div>
)
}
//注意事项与渲染过程:
import { useState } from "react";
// 组件的更新
/*
当调用setCount时 更新过程
首次渲染
组件内部代码会被执行一次 useState也会跟着执行 注意:初始值0只在首次渲染时生效
更新渲染 setCount都会更新
1.app组件会再次渲染 这个函数会再次执行
2.useState再次执行得到的新的count值不是0 是修改后的值 以此类推
*/
/*
useState注意事项:
1.useState可以执行多次 每次执行互相独立,每调用一次为函数组件提供一个状态
2.只能出现在函数组件中
3.不能嵌套在if/for/其他函数中(react按照hooks调用顺序识别每一个hook) 必须在函数组件最外层使用
*/
function App() {
// count 数据状态
// setCount 修改count的函数(专有函数)
const [count, setCount] = useState(0) //只能在函数组件最外层使用
const [flag, setFlag] = useState(true)
const [list, setList] = useState([])
console.log(count, flag, list);
function test() {
setCount(count + 1)
setFlag(!flag)
setList([1,2,3])
}
return (
<div>
count:{count}
flag:{flag ? '1' : '0'}
list:{list.join('-')}<button onClick={test}>修改</button>
</div>
)
}
export default App
//回调函数的参数场景
初始数据需要计算时候
const [name,setName]=useState(()=>{
//编写计算逻辑
return '计算后的值'
})
2.useEffect 副作用(操作外部)
函数副作用
指函数中的代码会对函数外部的内容进行更改,在react中副作用如:ajax请求 手动操作dom localstorage操作
useEffect(() => {
// 副作用函数的内容
})
组件初始化时先执行一次 等到每次数据修改组件更新再执行
--------------------
useEffect(() => {
// 副作用函数的内容
}, [])
组件初始化时先执行一次 只执行第一次
--------------------
useEffect(() => {
// 副作用函数的内容
}, [依赖项]) //依赖项可以有多个
1 组件初始化时执行一次 2 每次依赖项的值变化时执行
/*
注意事项
1.useEffect只能是一个同步函数 不能使用async
//如果发送请求或者异步操作 需要额外加一个箭头函数包裹
useEffect(()=>{
const getData = async () => {
const res = await xxx()
}
getData()
}, [])
2. 副作用函数的返回值
useEffect 函数是可以return的格式是下图,他可以返回一个函数,这个函数称为清理函数
useEffect(() => {
// 副作用函数的内容
return 副作用函数的返回值
}, [])
*/
案例
import { useState, useEffect } from "react";
// 在修改数据后 把count值放到页面标题中
// 1.导入useEffect函数
// 2.在函数组件中执行 传入回调
// 3.当我们修改状态更新组件时 副作用也会不断执行
// 依赖项控制副作用的执行时机
// 1.默认状态(无依赖项)
// 组件初始化时先执行一次 等到每次数据修改组件更新再执行
// 2.添加空数组依赖项
// 组件初始化时候执行一次 后续不执行
// 3.添加特定依赖项
// 组件渲染时执行一次 依赖项发生变化重新执行
// 4.只要在useEffect回调函数中用到的数据就应该出现在依赖项数组中声明,否则可能会出现bug
// 某种意义上 hook的出现 就是不想用生命周期概念也可以写业务代码
function App() {
const [count, setCount] = useState(0)
const [name, setName] = useState('李四')
useEffect(() => {
// 定义副作用
document.title = count
console.log(name);
},[count])
return (
<div>
<button onClick={() => setCount(count + 1)}>{count}</button>
<button onClick={() => setName('张三')}>{name}</button>
</div>
)
}
export default App
//清理副作用
使用场景:组件被销毁时 如常见的定时器
useEffect(()=>{
console.log('副作用执行了')
//副作用函数的执行实际为:在下一次副作用函数执行之前执行
return ()=>{
//清理副作用
}
})
-----------------------------------------------------------------------------
//示例:
import { useEffect, useState } from 'react';
//子组件
function Test() {
useEffect(()=>{
var timer= setInterval(() => {
console.log('定时器执行了');
}, 1000);
//return回调函数 清理副作用
return ()=>{
clearInterval(timer)//清除定时器
}
})
return (
<div>
Test
</div>
)
}
//父组件
function App() {
const [flag, setFlag] = useState(true)
return (
<div>
{flag ? <Test /> : null}
<button onClick={() => setFlag(!flag)}>switch</button>
</div>
)
}
export default App
3. hooks使用
//需求描述:自定义一个hook函数 实现获取滚动距离Y
//const [y]=useWindowScroll() y就是滚动到顶部的距离
1.新建hooks文件夹下 useWindow.js //自定义一个hook
//引入useState方法
import { useState } from "react"
export function useWindowScroll (){
const [y,setY] =useState(0) //数据与更新数据方法
window.addEventListener('scroll',()=>{
const h= document.documentElement.scrollTop //获取滚动到顶部高度
setY(h) //更新数据
})
return [y]
}
2.引入获取高度的该hook方法
import { useWindowScroll } from './hooks/useWindowScroll'
function App() {
const [y] = useWindowScroll()
console.log(y);
return (
<div style={{ height: '12000px' }}>
{y}
</div>
)
}
export default App
//需求 更新数据在本地存储一份
1.定义一个hook
//导入useEffect useState方法 修改数据与操作外部方法
import { useEffect, useState } from "react";
export function useLocalStorage(key,defaultValue) {
const [message, setMessage] = useState(defaultValue)
// 每次只要message变化 就会自动同步到本地localStorage (使用副作用useEffect函数操作本地缓存)
useEffect(() => {
window.localStorage.setItem(key, message)
}, [message,key])
return [message, setMessage]
}
2.引用hook
import { useLocalStorage } from './hooks/useLocalStorage';
function App() {
const [message,setMessage] =useLocalStorage('hook-key','阿飞')
setTimeout(() => {
setMessage('张三')
}, 5000);
return (
<div>
{message}
</div>
)
}
export default App
4. useRef(获取dom)
步骤:
1.导入useRef函数
2.执行useRef函数传入null,返回值为一个对象,内部有current属性存放拿到的dom对象(组件实例
3.通过ref绑定要获取的元素或者组件
//1.引用useRef
import React, { useEffect, useRef } from "react"
class Test extends React.Component {
state={
name:"jack"
}
getName=()=>{
console.log('this is child Test');
}
render() {
return (
<div>
类组件
</div>
)
}
}
function App() {
//3.获取该元素
const testRef = useRef(null)
const h1 = useRef(null)
useEffect(() => {
console.log(testRef);
testRef.current.getName() //调用组件方法
console.log(testRef.current.state.name); //获取组件上的数据/状态
console.log(h1);
}, [])
return (
<div style={{ height: '12000px' }}>
{/* 2.绑定ref */}
<Test ref={testRef} />
<h1 ref={h1}>this is h1</h1>
</div>
)
}
export default App
5.useContext(跨组件通信)
import React, { useState, createContext, useContext } from "react"
// App->A->C
// App数据->C
实现步骤:
1.使用createContext创建Context对象
2.顶层组件通过Provider提供数据 (数据静态不变可以index.js包裹 、 数据需要变化可以app.js)
3.底层组件通过useContext(Context)函数获取数据
//const { Provider, Consumer } = createContext()
const Context = createContext() //Context中包含提供者Provider与消费者Consumer
// 子组件A
function ComA() {
const count = useContext(Context)//使用useContext获取Context身上的数据
return (
<div>
ComA
<br />
app传过来的数据为:{count}
<ComC />
</div>
)
}
// 孙组件C
function ComC() {
const count = useContext(Context)//使用useContext获取Context身上的数据
return (
<div>
ComC,获取爷组件数据:
<br />
{count}
</div>
)
}
// 父组件
function App() {
const [count, setCount] = useState(0)
return (
// 2.使用Provider包裹根组件 提供数据
<Context.Provider value={count}>
<div>
<ComA />
<button onClick={()=>{setCount(count+1)}}>count+1</button>
</div>
</Context.Provider>
)
}
export default App
12. react-router
1.简单demo
// 引入两个组件
import Home from "./Home";
import About from "./About";
// 进行路由配置
import { BrowserRouter, Link, Route, Routes } from 'react-router-dom'
function App() {
return (
<div className="App">
{/* 声明当前要用一个非Hash模式的路由 */}
<BrowserRouter>
{/* 指定跳转的组件 to用来配置路由地址 */}
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
{/* 路由出口 路由对应的组件会在这里进行渲染 */}
<Routes>
{/* 指定路径和组件的对应关系 path代表路径 element代表组件 */}
<Route path="/" element={<Home />}></Route>
<Route path="/about" element={<About />}></Route>
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
2. 核心模块-BrowerRouter与HashRouter
作用:包裹整个应用 一个React应用只需要使用一次
两种常用Router : HashRouter和BrowerRouter(推荐)
HashRouter
使用url的哈希值实现(http://localhost:3000/#/first
BrowerRouter(推荐)
使用h5的history.pushState API实现(http://localhost:3000/first
3. 核心模块-Link
//to指定导航链接
<Link to="/about">关于</Link>
4. 核心组件-Routes
{/* 作用:路由出口 路由对应的组件会在这里进行渲染 */}
<Routes>
{/* 指定路径和组件的对应关系 path代表路径 element代表组件 */}
<Route path="/" element={<Home />}></Route>
<Route path="/about" element={<About />}></Route>
</Routes>
5. 核心组件-Route
//作用:用于指定导航链接 完成路由匹配
{/* 语法:指定路径和组件的对应关系 path代表路径 element代表组件 */}
<Route path="/about" element={<About />}></Route>
6.编程式导航
// 1.导入useNavigate
import { useNavigate } from "react-router-dom"
function Login() {
// 2.执行useNaviget得到一个跳转函数
const navigate = useNavigate()
function goAbout() {
//3.路由跳转 跳转时不添加到历史记录可以设置replace为true
navigate('/about',{replace:true})
}
return (
<div>
login
<button onClick={goAbout}>跳到关于</button>
</div>
)
}
export default Login
1.路由传参
//方式1:searchParams传参(推荐)
//路由传参
navigate('/about?id=1')
//取参
let [params] =useSearchParams()
let id=params.get('id')
//方式二:params传参
navigate('/about/1')
//取参
let params=useParams()
let id=params.id
//注意:需要配置路由参数
<Route path="/about/:id" element={<About />}></Route>
2.嵌套路由
//App.js中
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Layout from "./Layout";
import Login from "./Login";
import Board from "./Board";
import Article from "./Article";
function App() {
return (
<div className="App">
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
{/* 定义二级路由嵌套 */}
{/* 默认二级 添加index属性 把它自己的path去掉 */}
<Route index element={<Index />}></Route>
<Route path="board" element={<Board />}></Route>
<Route path="article" element={<Article />}></Route>
</Route>
<Route path="/login" element={<Login />}></Route>
{/* 当所有路径都不匹配 跳转到此页面 用来配置404页面 */}
<Route path="*" element={<NotFound />}></Route>
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
//Layout.js中:(一级路由组件)
import { Outlet } from "react-router-dom"
function Layout() {
return (
<div>
Layout
{/* 二级路由出口 */}
<Outlet />
</div>
)
}
export default Layout
//Board.js中:
function Board(){
return (
<div>
this is board
</div>
)
}
export default Board
13. mobx(类似vue中的vuex)
//集中状态管理工具,相当于vue与vuex关系
//同类工具还有redux dva recoil
Mobx是一个独立的响应式的库,可以独立于任何UI框架而存在,但是通常与react框架绑定使用,使用Mobx,使用Mobx来做响应式的数据建模,reacat作为UI视图渲染内容。
三部分:
1.react项目环境
2.mobx本身
3.一个链接mobx与react的中间件 mobx-react-lite
1.初始化过程:
声明数据->响应式处理->定义actions函数->实例化导出
2.mobx如何配合react,需要依赖什么包
mobx-react-lite作为链接包,导出observer方法,包裹组件(只能和函数组件配合)
3.模块化解决了什么问题
维护性问题
4.如何实现mobx的模块化
按功能拆分store模块,根模块中组合子模块,利用context机制依赖注入
1. 简单用法
//根目录新建store文件夹下顶一个一个counter.js状态管理
import { makeAutoObservable } from "mobx"
class CounterStore {
// 1.定义数据
count = 0
constructor() {
// 2.把数据弄成响应式
makeAutoObservable(this)
}
// 3.定义action函数(修改数据)
addCount=()=>{
this.count++
}
}
// 4.实例化 然后导出给react使用
const counterStore=new CounterStore()
export {counterStore}
---------------------------------------------------------------------------------------
//App.js中:
//1.导入counterStore
import { counterStore } from "./store/counter";
//2.导入中间件链接mobx与react完成响应式变化
import { observer } from "mobx-react-lite"
function App() {
return (
<div className="App">
{/*把store中的count渲染一下 */}
{/* 点击触发actions函数修改count值 */}
<button onClick={counterStore.addCount}>
{counterStore.count}
</button>
</div>
);
}
//3.包裹App
export default observer(App);
2. computed(计算属性)
//根目录新建store文件夹下顶一个counter.js状态管理
import { makeAutoObservable } from "mobx"
class CounterStore {
// 1.定义数据
count = 0
// 定义一个原始数据 list
list = [1, 2, 3, 4, 5, 6]
constructor() {
// 2.把数据弄成响应式
makeAutoObservable(this)
}
// 定义计算属性(加一个get修饰符即可)
get filterList() {
return this.list.filter(item => item > 2)
}
// 3.定义action函数(修改数据)
// 修改list
addList = () => {
this.list.push(7, 8, 9)
}
addCount = () => {
this.count++
}
}
// 4.实例化 然后导出给react使用
const counterStore = new CounterStore()
export { counterStore }
---------------------------------------------------------------------------------
//App.js中:
import { counterStore } from "./store/counter";
import { observer } from "mobx-react-lite"
function App() {
return (
<div className="App">
{/*把store中的count渲染一下 */}
{/* 点击触发 */}
{/* 使用计算属性 */}
{counterStore.filterList.join('-')}
{/* 定义一个方法 */}
<button onClick={counterStore.addCount}>
{counterStore.count}
</button>
<button onClick={counterStore.addList}>修改数组</button>
</div>
);
}
export default observer(App);
3.模块化(复杂用法 大型项目)
子模块1
//在store文件夹下新建counter.Store.js
import { makeAutoObservable } from "mobx"
class CounterStore {
// 1.定义数据
count = 0
// 定义一个原始数据 list
list = [1, 2, 3, 4, 5, 6]
constructor() {
// 2.把数据弄成响应式
makeAutoObservable(this)
}
// 定义计算属性
get filterList() {
return this.list.filter(item => item > 2)
}
// 修改list
addList = () => {
this.list.push(7, 8, 9)
}
// 3.定义action函数(修改数据)
addCount = () => {
this.count++
}
}
// 4.实例化 然后导出给react使用
export { CounterStore }
子模块2
//在store文件夹下新建list.Store.js
import { makeAutoObservable } from "mobx"
class ListStore{
list=['react','vue']
constructor(){
makeAutoObservable(this)
}
addList=()=>{
this.list.push('angular')
}
}
export {ListStore}
根模块(组合子模块)
//在store文件加下新建index.js
// 组合子模块
// 封装统一导出的供业务使用的方法
import { ListStore } from "./list.Store";
import { CounterStore } from "./counter.Store";
import React from "react";
// 1.声明一个rootStore
class RootStore{
constructor(){
// 对子模块进行实例化操作
// 将来实例化根store时,根store有两个属性分别对应各自子模块实例对象
this.CounterStore=new CounterStore()
this.ListStore=new ListStore()
}
}
// 实例化操作
const rootStore =new RootStore()
// 使用react context机制 完成统一方法封装
// Context.Provider value={传递的数据}
// 查找机制:useContext 优先从Provider value找 如果找不到就会找createContext()方法传递过来的默认参数
const context =React.createContext(rootStore)
// 这个方法作用:通过useContex拿到rootStore实例对象
// useStore() -> rootStore
const useStore=()=>React.useContext(context)
export {useStore}
使用
//App.js中使用
import { useStore } from "./store";
//导入中间件链接mobx与react完成响应式变化
import { observer } from "mobx-react-lite"
function App() {
const rootStore = useStore()
// 注意:解构赋值 到store实例对象就可以了 防止破坏响应式
const {counterStore,ListStore}=useStore()
console.log({ rootStore });
return (
<div className="App">
{rootStore.ListStore.list.join('-')}
<button onClick={rootStore.CounterStore.addCount}>
{rootStore.CounterStore.count}
</button>
<button onClick={rootStore.ListStore.addList}>
修改数组
</button>
</div>
);
}
//包裹App
export default observer(App);