1. 项目初始化
使用指令创建一个react项目
$ npx create-react-app react-demo
- npx create-react-app 是固定命令,create-react-app是React脚手架的名称
- react-demo是项目名称
- npx 命令会帮助我们临时安装create-react-app包,然后初始化项目完成之后会自自动删掉,所以不需要全局安装create-react-app
## 使用 npx
npx create-react-app my-app --template typescript
## 使用 npm
npm init react-app my-app --template typescript
## 使用 yarn
yarn create react-app my-app --template typescript
直接使用 typescript
启动项目使用
$ yarn start
or
$ npm start
2. JSX中使用js表达式
const name = '小武'
const getAge = () => {
return 12
}
const flag = true
function App() {
return (
<div className="App">
App
<br></br>
{ name }
<br/>
{getAge()}
<br/>
{flag ? '你好' : '你坏'}
</div>
);
}
export default App;
- 字符串、数值、布尔值、null、undefined、object( [] / {} )
- 1 + 2、'abc'.split('')、['a', 'b'].join('-')
- fn()
注意
if 语句/ switch-case 语句/ 变量声明语句,这些叫做语句,不是表达式,不能出现在 {} 中!!
- fn()
3. 列表渲染
// 注意:遍历列表时同样需要一个类型为number/string不可重复的key,提高diff性能
// key仅仅内部使用,不会出现在真实dom元素上
const sports = [
{id: 1, name: '足球'},
{id: 2, name: '篮球'},
{id: 3, name: '排球'},
]
function App() {
return (
<div className="App">
<ul>
{
sports.map(item => <li key={item.id}>{item.name}</li>)
}
</ul>
</div>
);
}
export default App;
4. 条件渲染
// 条件渲染 三元表达式 逻辑&&
// 复杂的多分支条件渲染 通过一个函数来写分支逻辑,模板中只负责调用
const flag = true
const showHtag = (type) => {
switch (type) {
case 1:
return <h1>这是 h1</h1>
case 2:
return <h2>这是 h2</h2>
case 3:
return <h3>这是 h3</h3>
default:
return <p>默认是 p标签</p>
}
}
function App() {
return (
<div className="App">
{ flag ? (
<div>
<span>这里是三元运算显示的内容</span>
</div>) : null
}
{
true && (<span>这里是逻辑&&实现的显示</span>)
}
{ showHtag() }
{ showHtag(1) }
{ showHtag(2) }
{ showHtag(3) }
</div>
);
}
export default App;
5. JSX样式处理
// JSX样式处理
// 1. 行内样式 - style 直接写在模板里面是双括号,内层括号表示对象
// 2. 类名 - className(推荐)方便动态控制
import "./app.css"
const styleObj = {
color: 'blue',
fontSize: 20
}
const activeClass = true
function App() {
return (
<div className="App">
<p>行内样式</p>
<div style={{color: 'red', fontSize: 18}}>测试行内样式1</div>
<div style={styleObj}>测试行内样式2</div>
<p>类名样式</p>
<div className={ activeClass ? "active" : ''}>测试类名样式</div>
</div>
);
}
export default App;
JSX注意事项
- JSX必须有一个根节点,如果没有根节点,可以使用<></>(幽灵节点)替代
- 所有标签必须形成闭合,成对闭合或者自闭合都可以
- JSX中的语法更加贴近JS语法,属性名采用驼峰命名法 class -> className for -> htmlFor
- JSX支持多行(换行),如果需要换行,需使用() 包裹,防止bug出现
6. 组件事件
import React from 'react';
// 定义函数组件
// 在传递自定义参数时
// 只需要一个额外参数时 {clickHandler} -> {() => clickHandler('自定义参数')}
// 急需要e也需要额外参数 {(e) => clickHandler(e, '自定义参数')}
function HelloFn() {
const sayHello = (e, message) => {
e.preventDefault();
console.log('你好', e, message);
};
return (
<div>
这是函数组件!
<a onClick={(e) => sayHello(e, '额外参数')} href='https://www.baidu.com/'>
百度
</a>
</div>
);
}
// 定义类组件
class HelloC extends React.Component {
sayYes = () => {
// 这里的this指向的是正确的当前的组件实例对象
// 可以非常方便的通过this关键词拿到组件实例身上的其他属性或者方法
console.log(this);
console.log('Yes');
};
clickHandler1() {
// 这里的this 不指向当前的组件实例对象而指向undefined 存在this丢失问题
console.log(this);
}
render() {
return <div onClick={this.sayYes}>创建一个类组件</div>;
}
}
// 定义类组件
function App() {
return (
<div className='App'>
{/* 渲染函数组件 */}
<HelloFn />
<HelloFn></HelloFn>
{/* 渲染类组件 */}
<HelloC />
<HelloC></HelloC>
</div>
);
}
export default App;
注意:
函数组件
- 组件的名称必须首字母大写,react内部会根据这个来判断是组件还是普通的HTML标签
- 函数组件必须有返回值,表示该组件的 UI 结构;如果不需要渲染任何内容,则返回 null
- 组件就像 HTML 标签一样可以被渲染到页面中。组件表示的是一段结构内容,对于函数组件来说,渲染的内容是函数的返回值就是对应的内容
- 使用函数名称作为组件标签名称,可以成对出现也可以自闭合
类组件 - 类名称也必须以大写字母开头
- 类组件应该继承 React.Component 父类,从而使用父类中提供的方法或属性
- 类组件必须提供 render 方法render 方法必须有返回值,表示该组件的 UI 结构
7. 组件状态
// 组件状态 类组件演示
import React from 'react';
class TestComponent extends React.Component {
// 初始化状态
// 可以定义各种属性,全都是当前组件的状态
state = {
name: '小武',
};
changeName = () => {
// 修改state中的name
// 修改状态, 不要直接修改state中的值,必须通过setState方法进行修改
this.setState({
name: '小亮',
});
};
render() {
return (
<div>
{/* 读取状态 */}
这是 TestComponent 当前name为:{this.state.name}
<button onClick={this.changeName}>修改name</button>
</div>
);
}
}
class Counter extends React.Component {
state = {
count: 0,
list1: [1, 2, 3, 4, 5],
list2: [1, 2, 3, 4, 5],
person: {
name: '小熊女',
age: 12,
},
};
changeCount = () => {
this.setState({
count: this.state.count + 1, // 操作简单数据类型
list1: [...this.state.list1, 6, 7], // 增加列表
list2: this.state.list2.filter((item) => item % 2), // 删除列表中某个元素
person: {
...this.state.person,
// 覆盖原来的属性 就可以达到修改对象中属性的目的
name: '二丫头',
},
});
};
render() {
return (
<>
<ul>
{this.state.list1.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
<ul>
{this.state.list2.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
<div>{this.state.person.name}</div>
<button onClick={this.changeCount}>点击了{this.state.count}次</button>
</>
);
}
}
/**
* 总结:
* 虽然现在大多数使用的是函数类组件,但老项目的维护可能需要类组件
* 1. 编写组件其实就是编写原生js类或者函数
* 2. 定义状态必须通过state 实例属性的方法 提供一个对象 名称是固定的就叫做state
* 3. 修改state中的任何属性 都不可以直接赋值 必须通过setState方法 这个方法来自继承得到
* 4. 这里的this关键词 很容易出现指向错误的问题 上面的写法是最推荐和最规范的 没有this指向问题
*
*/
const App = () => {
return (
<div className='App'>
<TestComponent />
<Counter />
</div>
);
};
export default App;
08. 受控组件
import React from 'react';
class Counter extends React.Component {
// 1. 声明用来控制input value的react组件状态
state = {
message: '信息',
};
inputChange = (e) => {
console.log('input触发change事件', e);
// 4. 拿到输入框最新值交给state中的message
this.setState({
message: e.target.value,
});
};
render() {
return (
// 2. 给input框的value属性绑定 react state
// 3. 给input框绑定一个change事件,为了拿到输入框中的数据
<input
type='text'
value={this.state.message}
onChange={this.inputChange}
/>
);
}
}
const App = () => {
return (
<div>
<Counter />
</div>
);
};
export default App;
09.非受控组件
import React, { createRef } from 'react';
class Counter extends React.Component {
// 非受控组件通过类似 vue 的 ref 属性获取元素
// 类组件通过 createRef 操作内部元素
myInpu = createRef();
getMsg = () => {
console.log(this.myInpu.current.value);
};
render() {
return (
<>
<input type='text' ref={this.myInpu} />
<button onClick={this.getMsg}>获取input信息</button>
</>
);
}
}
const App = () => {
return (
<div>
<Counter />
</div>
);
};
export default App;
10. 组件练习
import './index.css';
import avatar from './images/avatar.png';
import React from 'react';
import { v4 as uuid } from 'uuid';
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: '',
};
formateTime = (time) => {
return `${time.getFullYear()}-${time.getMonth() + 1}-${time.getDate()}`;
};
switchTab = (type) => {
this.setState({
active: type,
});
};
commentInput = (e) => {
// 获取评论
this.setState({
comment: e.target.value,
});
};
submitComment = () => {
// 发表评论
this.setState({
list: [
...this.state.list,
{
id: uuid(),
author: '小亮',
comment: this.state.comment,
time: new Date(),
// 1: 点赞 0:无态度 -1:踩
attitude: 0,
},
],
});
};
// 删除评论
delComment = (id) => {
console.log(id);
this.setState({
list: this.state.list.filter((item) => item.id !== id),
});
};
// 点赞与否
switchLike = (user, type) => {
const { id, attitude } = user;
this.setState({
list: this.state.list.map((item) => {
if (item.id === id) {
let isLike = 0;
if (type === 'like') {
isLike = attitude === 1 ? 0 : 1;
} else {
isLike = attitude === -1 ? 0 : -1;
}
return {
...item,
attitude: isLike,
};
} else {
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
key={tab.id}
onClick={() => this.switchTab(tab.type)}
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'
placeholder='发条友善的评论'
className='ipt-txt'
value={this.state.comment}
onChange={this.commentInput}
/>
<button className='comment-submit' onClick={this.submitComment}>
发表评论
</button>
</div>
<div className='comment-emoji'>
<i className='face'></i>
<span className='text'>表情</span>
</div>
</div>
{this.state.list.map((user) => (
<div className='comment-list' key={user.id}>
<div className='list-item'>
<div className='user-face'>
<img className='user-head' src={avatar} alt='' />
</div>
<div className='comment'>
<div className='user'>{user.author}</div>
<p className='text'>{user.comment}</p>
<div className='info'>
<span className='time'>{this.formateTime(user.time)}</span>
<span
onClick={() => this.switchLike(user, 'like')}
className={user.attitude === 1 ? 'like liked' : 'like'}
>
<i className='icon' />
</span>
<span
onClick={() => this.switchLike(user, 'hate')}
className={user.attitude === -1 ? 'hate hated' : 'hate'}
>
<i className='icon' />
</span>
<span
className='reply btn-hover'
onClick={() => this.delComment(user.id)}
>
删除
</span>
</div>
</div>
</div>
</div>
))}
</div>
</div>
);
}
}
export default App;
11. 组件通信父传子
import React from 'react';
// 1. props是只读对象(readonly)
// 根据单项数据流的要求,子组件只能读取props中的数据,不能进行修改
// 2. props可以传递任意数据
// 数字、字符串、布尔值、数组、对象、函数、JSX
// function SonFc(props) {
// return (
// <div>
// 这是函数式子组件--{props.mesg}--{props.age}--
// {props.isMan ? '传递了true的布尔值' : ''}--{props.list}--
// <button onClick={props.cb}>获取父组件传递的函数</button>
// --这是传递的jsx的内容,浏览器展示的是真实的dom,类似vue插槽:{props.child}
// </div>
// );
// }
// 解构获取
function SonFc({ mesg, age, isMan, list, cb, child }) {
return (
<div>
这是函数式子组件--{mesg}--{age}--
{isMan ? '传递了true的布尔值' : ''}--{list}--
<button onClick={cb}>获取父组件传递的函数</button>
--这是传递的jsx的内容,浏览器展示的是真实的dom,类似vue插槽:{child}
</div>
);
}
class SonCl extends React.Component {
render() {
return <div>这是类子组件--{this.props.mesg}</div>;
}
}
class App extends React.Component {
state = {
mesg: '父组件传递给子组件的值',
};
getMes = () => {
console.log('传递函数');
};
render() {
return (
<div className='App'>
<SonFc
mesg={this.state.mesg}
age={20}
isMan={true}
list={[1, 2, 3, 4]}
cb={this.getMes}
child={<span>this is child</span>}
/>
<SonCl mesg={this.state.mesg} />
</div>
);
}
}
export default App;
12. 子传父
父组件给子组件传递回调函数,子组件调用
import React from 'react';
function SonFc(props) {
const { getSonMes } = props;
function clickHandler() {
getSonMes('传递数据给父组件');
}
return (
<div>
子组件
<button onClick={clickHandler}>传递数据给父组件</button>
<button onClick={() => getSonMes('传递数据给父组件')}>
传递数据给父组件
</button>
</div>
);
}
class App extends React.Component {
state = {
mesg: '父组件传递给子组件的值',
};
getSonMes = (sonMsg) => {
console.log(sonMsg);
};
render() {
return (
<div className='App'>
<SonFc getSonMes={this.getSonMes} />
</div>
);
}
}
export default App;
13. 兄弟通过父组件传值
import React from 'react';
// 组件B通过父组件给A组件传数据
function SonA(props) {
return (
<div style={{ border: '1px solid red' }}>
<h3>子组件A</h3>
{props.mesg}
</div>
);
}
function SonB(props) {
const state = {
message: '这是B组件来的',
};
return (
<div style={{ border: '1px solid blue' }}>
<h3>子组件B</h3>
<button onClick={() => props.getSonMes(state.message)}>
给A组件传值
</button>
</div>
);
}
class App extends React.Component {
state = {
mesg: '',
};
getSonMes = (sonMsg) => {
this.setState({
mesg: sonMsg,
});
};
render() {
return (
<div className='App'>
<SonA mesg={this.state.mesg} />
<SonB getSonMes={this.getSonMes} />
</div>
);
}
}
export default App;
14. 跨组件通信
import React, { createContext } from 'react';
const { Provider, Consumer } = createContext();
// 跨组件通信
// 上层组件和下层组件关系是相对的,只要存在即可,通常会将根组件作为数据提供方
// 语法是固定的,提供的位置 value提供数据 获取的位置 {value => 使用value接收 }
function ComA() {
return (
<div style={{ border: '1px solid red' }}>
<h3>组件A</h3>
<Consumer>{(value) => <span>{value}</span>}</Consumer>
<ComB />
</div>
);
}
function ComB() {
return (
<div style={{ border: '1px solid blue' }}>
<h3>组件B</h3>
<Consumer>{(value) => <span>{value}</span>}</Consumer>
</div>
);
}
class App extends React.Component {
state = {
mesg: '根组件传的值',
};
render() {
return (
<Provider value={this.state.mesg}>
<div className='App'>
这是根组件App
<ComA />
</div>
</Provider>
);
}
}
export default App;
15. 阶段小练习
import React from 'react';
// 跨组件通信
function ListItem({ name, price, info, id, removeItem }) {
return (
<div style={{ border: '1px solid red' }}>
<h3>{name}</h3>
<p>{price}</p>
<p>{info}</p>
<button onClick={() => removeItem(id)}>删除</button>
</div>
);
}
class App extends React.Component {
state = {
list: [
{
id: 1,
name: '超级好吃的棒棒糖',
price: 18.8,
info: '开业大酬宾,全场8折',
},
{
id: 2,
name: '超级好吃的大鸡腿',
price: 34.2,
info: '开业大酬宾,全场8折',
},
{
id: 3,
name: '超级无敌的冰激凌',
price: 14.2,
info: '开业大酬宾,全场8折',
},
],
};
removeItem = (id) => {
this.setState({
list: this.state.list.filter((item) => item.id !== id),
});
};
render() {
return (
<div className='App'>
{this.state.list.map((item) => (
<ListItem
key={item.id}
{...item}
removeItem={this.removeItem}
></ListItem>
))}
</div>
);
}
}
export default App;
16. children属性
import React from 'react';
// 跨组件通信
function ListItem({ children }) {
// children(); // children 传入的是函数时就不能放到标签中了
return <div style={{ border: '1px solid red' }}>ListItem {children}</div>;
}
class App extends React.Component {
render() {
return (
<div className='App'>
<ListItem>
{/* children 可以是普通文本、普通标签元素、函数 / 对象、JSX,多个child在组件的children中会是数组形式,可以进行数组的操作*/}
{/* this is string */}
<div>this is child</div>
<p>this is p</p>
{/* {() => console.log(123)} */}
{/* {
<div>
<p>{'this is p'}</p>
</div>
} */}
</ListItem>
</div>
);
}
}
export default App;
17. props校验
import React from 'react';
import PropTypes from 'prop-types';
// props校验-场景和使用
// 1. 安装属性校验包:yarn add prop-types
// 2. 导入prop-types 包
// 3. 使用 组件名.propTypes = {} 给组件添加校验规则
// // 常见类型
// optionalFunc: PropTypes.func,
// // 必填 只需要在类型后面串联一个isRequired
// requiredFunc: PropTypes.func.isRequired,
// // 特定结构的对象
// optionalObjectWithShape: PropTypes.shape({
// color: PropTypes.string,
// fontSize: PropTypes.number
// })
const List = (props) => {
const arr = props.list;
return (
<ul>
{arr.map((item) => (
<li key={item} style={props.style}>
{item}
</li>
))}
</ul>
);
};
// props校验-默认值
// 函数组件
function List1({ pageSize = 10 }) {
return <div>此处展示props的默认值:{pageSize}</div>;
}
// 类组件
class List2 extends React.Component {
static defaultProps = {
pageSize: 10,
};
render() {
return <div>此处展示props的默认值:{this.props.pageSize}</div>;
}
}
List.propTypes = {
list: PropTypes.array.isRequired,
style: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number,
}),
};
class App extends React.Component {
state = {
list: [1, 2, 3, 4],
};
render() {
return (
<div className='App'>
<List
list={this.state.list}
style={{ color: 'red', fontSize: 24 }}
></List>
<List1></List1>
<List2></List2>
</div>
);
}
}
export default App;
18. 类组件的生命周期
import React from 'react';
// 有生命周期(类组件 实例化 函数组件没有生命周期 不需要实例化)
// 挂载时会先后执行 constructor、render、componentDidMount
// 更新时会先后执行 render、componentDidUpdate
// 卸载时执行 componentWillUnmount
// 生命周期 - 挂载阶段
// constructor 创建组件时,最先执行,初始化的时候只执行一次 1. 初始化state 2. 创建 Ref 3. 使用 bind 解决 this 指向问题等
// render 每次组件渲染都会触发 渲染UI(注意: 不能在里面调用setState() )
// componentDidMount 组件挂载(完成DOM渲染)后执行,初始化的时候执行一次 1. 发送网络请求 2.DOM操作
class ComA extends React.Component {
// 如果数据在组件中和视图有联系,定义到state中
// 而如果数据状态不与视图绑定 定义成一个普通的实例属性即可
// state中尽量保持精简
timer = '';
componentDidMount() {
this.timer = setInterval(() => {
console.log('定时1秒触发');
}, 1000);
}
componentWillUnmount() {
console.log('componentWillUnmount');
// 清除定时器
clearInterval(this.timer);
}
render() {
return <div>挂载实例</div>;
}
}
class App extends React.Component {
constructor() {
super();
console.log('constructor');
}
componentDidMount() {
console.log('componentDidMount');
// 类似于 vue 的mounted
}
componentDidUpdate() {
// 不要直接调用setState
console.log('componentDidUpdate');
}
state = {
count: 0,
show: true,
};
addCount = () => {
this.setState({
count: this.state.count + 1,
show: !this.state.show,
});
};
render() {
console.log('render');
return (
<div className='App'>
this is App
<button onClick={this.addCount}>{this.state.count}</button>
{this.state.show ? <ComA /> : null}
</div>
);
}
}
export default App;
19. 删、查练习
import React from 'react';
import './App.css';
import { Input, Popconfirm, Table } from 'antd';
import axios from 'axios';
const { Search } = Input;
// 需要启动指令 yarn mock-serve 启动本地后台
class App extends React.Component {
state = {
list: [],
columns: [
{
title: '任务编号',
dataIndex: 'id',
key: 'id',
},
{
title: '任务名称',
dataIndex: 'name',
key: 'name',
},
{
title: '任务描述',
dataIndex: 'des',
key: 'des',
},
{
title: '操作',
dataIndex: 'do',
key: 'do',
render: (_, record) =>
this.state.list.length > 1 ? (
<Popconfirm
title='确定删除吗?'
onConfirm={() => this.delOneData(_, record)}
>
<a>删除</a>
</Popconfirm>
) : null,
},
],
};
// 搜索
onSearch = async (value) => {
const res = await axios.get(`http://localhost:3001/data/?q=${value}`);
this.setState({
list: res.data,
});
};
// 删除
delOneData = async (_, record) => {
await axios.delete(`http://localhost:3001/data/${record.id}`);
this.loadData();
};
// 加载列表
loadData = async () => {
const res = await axios.get('http://localhost:3001/data');
res.data.forEach((item, index) => {
item.key = index + 1;
});
this.setState({
list: res.data,
});
};
componentDidMount() {
this.loadData();
}
render() {
return (
<div className='App'>
<Search
placeholder='请输入搜索内容'
allowClear
enterButton='搜索'
size='large'
onSearch={this.onSearch}
/>
<Table columns={this.state.columns} dataSource={this.state.list} />
</div>
);
}
}
export default App;
20 hook-useState
import React, { useState } from 'react';
// useState为函数组件提供状态(state)
// 状态的读取与修改
// const [count, setCount] = useState(0)
// 1. useState 传过来的参数作为 count 的初始值
// 2. [count, setCount] 这里的写法是一个解构赋值 useState 返回值是一个数组
// 名字可以自己定义
// 顺序不可交换 第一个参数是数据状态 第二个参数是修改数据的方法
// 3. setCount函数 作用用来修改 count 依旧保持不能直接修改原值,还是生成一个新值替换原值
// 4. count 和 setCount 是一对 是绑定在一起的 setCount 只能用来修改对应的 count 值
// 当调用setCount时 更新过程
// 首次渲染
// 首次被渲染时,组件内部的代码会被执行一次
// 其中的 useState 也会跟着执行 注意:初始值只在首次渲染时生效
// 更新渲染 setCount 都会更新
// 1. app 组件会再次渲染 这个函数会被再次执行
// 2. useState 再次执行 得到新的count值 不是0 而是修改之后的1 模板会用新值渲染
// useState 初始值只在首次渲染生效 后续只要调用 setCount 整个 app 组件中的代码都会执行, 此时的 count 每次拿到的都是最新值
// useState 的调函数中的逻辑只会在组件初始化的时候执行一次
// 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过计算才能获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用
function getDefaultV(v) {
for (let i = 1; i < 10000; i++) {}
return v;
}
function Counter(props) {
const [count, setCount] = useState(() => {
// 在首次渲染时需要大量计算,在后面这个计算就不需要了
// return props.count;
return getDefaultV(props.count);
});
return (
<div>
<button onClick={() => setCount(count + 1)}>{count}</button>
</div>
);
}
const App = () => {
// 参数:状态初始值比如,传入 0 表示该状态的初始值为 0
// 返回值:数组,包含两个值:1 状态值(state) 2 修改该状态的函数(setState)
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(true);
const [list, setList] = useState([]);
console.log('执行了');
// 1. useState 函数可以执行多次,每次执行互相独立,每调用一次为函数组件提供一个状态
// 2. useState 注意事项
// a. 只能出现在函数组件或者其他hook函数中
// b. 不能嵌套在if/for/其它函数中(react按照hooks的调用顺序识别每一个hook)
// c. 可以通过开发者工具查看hooks状态
function test() {
setCount(count + 1);
setFlag(!flag);
setList([...list, 1]);
}
return (
<div>
<div>count: {count}</div>
<div>flag: {flag ? 'true' : 'false'}</div>
<div>list: {list.join('-')}</div>
<button onClick={() => test()}>修改</button>
<Counter count={10}></Counter>
<Counter count={20}></Counter>
</div>
);
};
export default App;
21. hook-useEffect
import { useState, useEffect } from 'react';
import axios from 'axios';
// 对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用(比如,手动修改 DOM)
// useEffect函数的作用就是为react函数组件提供副作用处理的!
// 通过修改状态更新组件时,副作用也会不断执行
const App = () => {
const [count, setCount] = useState(0);
const [name, setName] = useState('小亮');
// 1. 此时无依赖项 组件首次渲染执行一次,以及不管是哪个状态更改引起组件更新时都会重新执行
// useEffect(() => {
// // 副作用 dom操作
// document.title = `当前已点击了${count}次`;
// });
// 2. 添加空数组 组件只在首次渲染时执行一次
// useEffect(() => {
// // 副作用 dom操作
// document.title = `当前已点击了${count}次`;
// }, []);
// 3. 添加特定依赖项 副作用函数在首次渲染时执行,在依赖项发生变化时重新执行 useEffect 回调函数中用到的数据(比如,count,name)就是依赖数据,就应该出现在依赖项数组中,如果不添加依赖项就会有bug出现
// useEffect(() => {
// document.title = `当前已点击了${count}次`;
// }, [count]);
// 4. 清理副作用
// 如果想要清理副作用 可以在副作用函数中的末尾return一个新的函数,在新的函数中编写清理副作用的逻辑
// 注意执行时机为:
// 1. 组件卸载时自动执行
// 2. 组件更新时,下一个useEffect副作用函数执行之前自动执行
// useEffect(() => {
// const timerId = setInterval(() => {
// setCount(count + 1);
// }, 1000);
// return () => {
// // 用来清理副作用的事情
// clearInterval(timerId);
// };
// }, [count]);
// useEffect - 发送网络请求
useEffect(() => {
async function fetchData() {
const res = await axios.get('http://geek.itheima.net/v1_0/channels');
console.log(res.data.data.channels);
}
fetchData();
}, []);
return (
<div>
<button onClick={() => setCount(count + 1)}>修改count{count}</button>
<button onClick={() => setName(name + '1')}>修改name</button>
</div>
);
};
export default App;
22. hook测试
app.js
import { useWindowScroll } from './useWindowScroll';
import { useLocalStorage } from './useLocalStorage';
const App = () => {
const [y] = useWindowScroll();
const [message, setMessage] = useLocalStorage('hook', '666');
const storageData = () => {
setMessage('777');
};
return (
<div style={{ height: '12000px' }}>
{y} / {message}
<button onClick={storageData}>存入数据</button>
</div>
);
};
export default App;
useLocalStorage.js
import { useState, useEffect } from 'react';
// 自动同步到本地LocalStorage
export function useLocalStorage(key, defaultValue) {
const [message, setMessage] = useState(defaultValue);
useEffect(() => {
window.localStorage.setItem(key, message);
}, [key, message]);
return [message, setMessage];
}
useWindowScroll.js
import { useState, useEffect } from 'react';
// 获取滚动距离Y
export function useWindowScroll() {
const [y, setY] = useState(0);
useEffect(() => {
const scrollHandler = () => {
const h = document.documentElement.scrollTop;
setY(h);
};
window.addEventListener('scroll', scrollHandler);
return () => window.removeEventListener('scroll', scrollHandler);
});
return [y];
}
23. useRef
import React, { useRef, useEffect } from 'react';
// useRef 获取真实dom或类组件实例
// 1. 导入 useRef 函数
// 2. 执行 useRef 函数并传入null,返回值为一个对象 内部有一个current属性存放拿到的dom对象(组件实例)
// 3. 通过ref 绑定 要获取的元素或者组件
// 函数组件由于没有实例,不能使用ref获取,如果想获取组件实例,必须是类组件
class Foo extends React.Component {
say = () => {
console.log('hello');
};
render() {
return <div>Foo</div>;
}
}
const App = () => {
const h1Ref = useRef(null);
const fooRef = useRef(null);
useEffect(() => {
console.log(h1Ref.current);
console.log(fooRef.current);
}, []);
return (
<div className='App'>
<h1 ref={h1Ref}>标签h1</h1>
<Foo ref={fooRef}></Foo>
</div>
);
};
export default App;
24. hook-useContext
单独使用 useContext 和 createContext
import React, { useContext, createContext, useState } from 'react';
// 创建 Context 对象 跨组件通信
const Context = createContext();
function ComA() {
return (
<div>
ComA
<br />
<ComB></ComB>
</div>
);
}
function ComB() {
// 底层组件通过useContext函数获取数据
const count = useContext(Context);
return (
<div>
ComB
<br />
{count}
</div>
);
}
const App = () => {
const [count, setCount] = useState(0);
return (
// 顶层组件通过Provider 提供数据
<Context.Provider value={count}>
<div className='App'>
<ComA></ComA>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
</Context.Provider>
);
};
export default App;
抽离
App.js
import React, { useContext } from 'react';
import Context from './contex';
// 创建 Context 对象
// const Context = createContext();
function ComA() {
return (
<div>
ComA
<br />
<ComB></ComB>
</div>
);
}
function ComB() {
// 底层组件通过useContext函数获取数据
const count = useContext(Context);
return (
<div>
ComB
<br />
{count}
</div>
);
}
const App = () => {
return (
// 顶层组件通过Provider 提供数据
<div className='App'>
<ComA></ComA>
</div>
);
};
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import Context from './contex';
// Context 如果要传递的数据 只需要在整个应用初始化的时候传递一次就可以选在在index.js文件中实现数据提供
// 如果 Context 需要传递数据并且将来还需要在对数据做修改 底层组件也需要跟着一起改变 就写道app.js
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Context.Provider value={100}>
<App />
</Context.Provider>,
);
contex.js
import { createContext } from 'react';
// 创建 Context 对象 跨组件通信
const Context = createContext();
export default Context;
25. react-router
App.js
import React from 'react';
import {
HashRouter,
BrowserRouter,
Routes,
Route,
Link,
} from 'react-router-dom';
import Home from './Home';
import About from './About';
// 添加路由首先需要安装 react-router 包
// yarn add react-router-dom
// BrowserRouter history 模式 h5的 history.pushState API实现
// HashRouter 监听url hash值实现
// * Link 指定跳转的组件 to用来配置路由地址
// * Routes 路由出口 路由对应的组件会在这里进行渲染
// * Route 指定路径和组件的对应关系 path代表路径 element代表组件 成对出现
const routes = [
{
path: '/',
name: '首页',
component: Home(),
},
{
path: '/about',
name: '关于',
component: About(),
},
];
const App = () => {
return (
// 这种路由是一个非hash模式的
<div className='App'>
{/* <BrowserRouter>
{routes.map((item, index) => (
<Link to={item.path} key={index}>
{item.name}
</Link>
))}
<Routes>
{routes.map((item, index) => (
<Route
path={item.path}
key={index}
element={item.component}
></Route>
))}
</Routes>
</BrowserRouter> */}
{/* <BrowserRouter>
<Link to='/'>首页</Link>
<Link to='/about'>关于</Link>
<Routes>
<Route path='/' element={<Home />}></Route>
<Route path='/about' element={<About />}></Route>
</Routes>
</BrowserRouter> */}
<HashRouter>
<Link to='/'>首页</Link>
<Link to='/about'>关于</Link>
<Routes>
<Route path='/' element={<Home />}></Route>
<Route path='/about' element={<About />}></Route>
</Routes>
</HashRouter>
</div>
);
};
export default App;
About.js
function About() {
return <div>this is About</div>;
}
export default About;
Home.js
function Home() {
return <div>this is Home</div>;
}
export default Home;
26. 编程式导航
App.js
import React from 'react';
import {
HashRouter,
BrowserRouter,
Routes,
Route,
Link,
} from 'react-router-dom';
import Home from './Home';
import About from './About';
import Login from './Login';
const App = () => {
return (
<div className='App'>
<BrowserRouter>
<Link to='/'>首页</Link>
<Link to='/about'>关于</Link>
<Routes>
<Route path='/' element={<Home />}></Route>
<Route path='/about' element={<About />}></Route>
<Route path='/login' element={<Login />}></Route>
</Routes>
</BrowserRouter>
</div>
);
};
export default App;
About.js
function About() {
return <div>this is About</div>;
}
export default About;
Home.js
function Home() {
return <div>this is Home</div>;
}
export default Home;
Login.js
import { useNavigate } from 'react-router-dom';
// navigate 要跳转到的路由
// 注: 如果在跳转时不想添加历史记录,可以添加额外参数replace 为true.
function Login() {
const navigate = useNavigate();
return (
<div>
Login
<button onClick={() => navigate('/about', { replace: true })}>
跳转到关于
</button>
</div>
);
}
export default Login;
27. 路由传参
App.js
import React from 'react';
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
import Home from './Home';
import About from './About';
import Login from './Login';
const App = () => {
return (
<div className='App'>
<BrowserRouter>
<Link to='/'>首页</Link>
<Link to='/about'>关于</Link>
<Routes>
<Route path='/' element={<Home />}></Route>
<Route path='/about/:id' element={<About />}></Route>
<Route path='/login' element={<Login />}></Route>
</Routes>
</BrowserRouter>
</div>
);
};
export default App;
Login.js
import { useNavigate } from 'react-router-dom';
// navigate 要跳转到的路由
// 注: 如果在跳转时不想添加历史记录,可以添加额外参数replace 为true.
// 1. searchParams 传参 通过路由的查询参数传递,接收在跳转的路由中 通过 useSearchParams 获取
// 2. params 传参 需要在路由处进行设置占位符
// 第一种和第二种在同一个路由下参数是不兼容的
function Login() {
const navigate = useNavigate();
return (
<div>
第一种参数
<button onClick={() => navigate('/about?id=1001&id=1002&name=PP')}>
跳转到关于
</button>
<br />
第二种传参
<button onClick={() => navigate('/about/10001')}>跳转到关于</button>
</div>
);
}
export default Login;
About.js
import { useSearchParams, useParams } from 'react-router-dom';
// 通过 useSearchParams 获取查询参数传递的数据
function About() {
const [params] = useSearchParams();
const res = params.getAll('id'); // 通过getAll可以获取所有参数值
const name = params.get('name'); // 通过get获取第一个需要的参数值
console.log(res);
let secParams = useParams();
return (
<div>
this is About获取到的第一种路由参数是{res}-{name}
<br />
this is About获取到的第二种路由参数是{secParams.id}
</div>
);
}
export default About;
Home.js
function Home() {
return <div>this is Home</div>;
}
export default Home;
28. 嵌套路由
App.js
import React from 'react';
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
import Login from './Login';
import Layout from './Layout';
import Board from './Board';
import Article from './Article';
import NotFound from './NotFound';
const App = () => {
return (
<div className='App'>
<BrowserRouter>
<Link to='/'>首页</Link>
<Link to='/login'>关于</Link>
<Routes>
<Route path='/' element={<Layout />}>
{/* 二级路由嵌套在一级路由下 */}
{/* 首次渲染完毕就需要显示的二级路由,把原本的路径path属性用index替换 */}
<Route index element={<Board />} />
{/* <Route path='board' element={<Board />}></Route> */}
<Route path='article' element={<Article />}></Route>
</Route>
<Route path='/login' element={<Login />}></Route>
{/* 当url的路径在整个路由配置中都找不到对应的path */}
<Route path='*' element={<NotFound />}></Route>
</Routes>
</BrowserRouter>
</div>
);
};
export default App;
Layout.js
import { Outlet, Link } from 'react-router-dom';
// 设置board为默认路由,那么,跳转的path也需要修改为默认路由
function Layout() {
return (
<div>
{/* 二级路由的path等于 一级path + 二级path */}
Layout - <Link to='/'>board</Link> -{' '}
{/* <Link to='/board'>board</Link> - */}
<Link to='/article'>artical</Link>
{/* 二级路由出口 */}
<Outlet />
</div>
);
}
export default Layout;
Article.js
function Article() {
return <div>Article</div>;
}
export default Article;
Board.js
function Board() {
return <div>Board</div>;
}
export default Board;
Login.js
function Login() {
return <div>Login</div>;
}
export default Login;
NotFound.js
function NotFound() {
return <div>对不起,页面丢失了</div>;
}
export default NotFound;
29. 集中式路由配置
App.js
import React from 'react';
import { BrowserRouter, Routes, Route, useRoutes } from 'react-router-dom';
import Login from './Login';
import Layout from './Layout';
import Board from './Board';
import Article from './Article';
import NotFound from './NotFound';
// 1. 准备一个路由数组 数组中定义所有的路由对应关系
const routesList = [
{
path: '/',
element: <Layout />,
children: [
{
element: <Board />,
index: true,
},
{
path: 'article',
element: <Article />,
},
],
},
{
path: '/login',
element: <Login />,
},
{
path: '*',
element: <NotFound />,
},
];
// 2. 使用useRoutes方法传入routesList生成Routes组件
function WrapperRoutes() {
let element = useRoutes(routesList);
return element;
}
const App = () => {
return (
<div className='App'>
<BrowserRouter>
{/* 3. 替换之前的Routes组件 */}
<WrapperRoutes />
</BrowserRouter>
</div>
);
};
export default App;
30. mobx
安装 mobx 和中间件工具 mobx-react-lite 只能函数组件中使用
yarn add mobx mobx-react-lite
是在 src 目录下新建一个 store 文件夹,在其中存放 mobx 逻辑
30-1 基本使用
App.js
import React from 'react';
// 使用store
// 导入counterStore
import { counter } from './store/counter';
// 导入observer方法
import { observer } from 'mobx-react-lite';
const App = () => {
return (
<div className='App'>
<div>{counter.count}</div>
<button onClick={counter.addCound}>+</button>
<div>{counter.getO.join('-')}</div>
<button onClick={counter.changeList}>修改数组</button>
</div>
);
};
// 包裹组件让视图响应数据变化
export default observer(App);
store\counter.js
import { makeAutoObservable, computed } from 'mobx';
// 通过get关键词 定义计算属性
class CounterStore {
count = 0; // 定义数据
list = [0, 1, 2, 3, 4, 5];
constructor() {
makeAutoObservable(this); // 响应式处理
}
// 定义修改数据的方法
addCound = () => {
this.count++;
console.log(this.count);
};
// 定义计算属性
get getO() {
return this.list.filter((item) => item > 2);
}
// 修改数组
changeList = () => {
let lth = this.list.length;
return this.list.push(lth);
};
}
const counter = new CounterStore();
export { counter };
30-2. 模块化
App.js
import { useEffect } from 'react';
import { useStore } from './store';
import { observer } from 'mobx-react-lite';
function App() {
const { channlStore, counterStore, listStore } = useStore();
// 1. 使用数据渲染组件
// 2. 触发action函数发送异步请求
useEffect(() => {
channlStore.setChannelList();
}, []);
return (
<>
<ul>
{channlStore.channelList.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
<br />
counterStore {counterStore.count}
<button onClick={counterStore.addCound}>+</button>
<br />
listStore {listStore.list.join('-')}
<button onClick={listStore.changeList}>修改ListStore</button>
</>
);
}
// 让组件可以响应数据的变化[也就是数据一变组件重新渲染]
export default observer(App);
store文件夹中:
index.js
import React from 'react';
import { channelStore } from './channelStore';
import { counterStore } from './counterStore';
import { listStore } from './listStore';
class RootStore {
constructor() {
this.channlStore = channelStore;
this.counterStore = counterStore;
this.listStore = listStore;
}
}
const rootStore = new RootStore();
// context机制的数据查找链 Provider如果找不到 就找createContext方法执行时传入的参数
const context = React.createContext(rootStore);
const useStore = () => React.useContext(context);
export { useStore };
channelStore.js
// 异步的获取
import { makeAutoObservable, observable, action } from 'mobx';
import axios from 'axios';
// 此处界面有警告
class ChannelStore {
channelList = [];
constructor() {
makeAutoObservable(this);
}
// 只要调用这个方法 就可以从后端拿到数据并且存入channelList
setChannelList = async () => {
const res = await axios.get('http://geek.itheima.net/v1_0/channels');
this.channelList = res.data.data.channels;
};
}
const channelStore = new ChannelStore();
export { channelStore };
counterStore.js
import { makeAutoObservable } from 'mobx';
// 通过get关键词 定义计算属性
class CounterStore {
count = 0; // 定义数据
list = [0, 1, 2, 3, 4, 5];
constructor() {
makeAutoObservable(this); // 响应式处理
}
// 定义修改数据的方法
addCound = () => {
this.count++;
console.log(this.count);
};
// 定义计算属性
get getO() {
return this.list.filter((item) => item > 2);
}
// 修改数组
changeList = () => {
let lth = this.list.length;
return this.list.push(lth);
};
}
const counterStore = new CounterStore();
export { counterStore };
listStore.js
import { makeAutoObservable } from 'mobx';
class ListStore {
list = ['react', 'vue'];
constructor() {
makeAutoObservable(this); // 响应式处理
}
// 修改数组
changeList = () => {
let lth = this.list.length - 1;
return this.list.push('框架' + lth);
};
}
const listStore = new ListStore();
export { listStore };