React小计
事件
// App.js
// rcc : 快捷代码块, 前提是安装了必备的 React 插件
import React, { Component } from "react";
// npm管理的项目, 之前的脚本 就化身为 模块
export default class App extends Component {
// 事件
show() {
alert("点击事件!");
return () => alert("我是返回值");
}
render() {
return (
<div>
<h1>Hello World!</h1>
{/*
点击事件:
原生开发: οnclick=""
vue开发: @click=""
小程序: bind-tap=""
*/}
{/* this.show: this代表当前对象 */}
<button onClick={this.show}>点击</button>
{/* 注意事项: {}中的代码在页面显示时会自动运行 */}
<div>{4 + 5}</div>
{/* 开发阶段: render() 会渲染两次 */}
{/* {this.show()} */}
</div>
);
}
}
事件中的this指向
// 事件中的this指向
import React, { Component } from "react";
export default class App extends Component {
name = "Lena";
// 普通函数 在严格模式下, 事件触发时, 其中this指向 undefined
show() {
console.log("this:", this);
console.log(this.name);
}
// 箭头函数: ES6新特性, 弥补了 普通函数this指向多变的缺点; 自带this指向保持
jiantou = () => {
console.log(this);
console.log(this.name);
};
// 组件生成的方式: new App().render()
render() {
console.log("render中的this:", this);
return (
<div>
{/* 事件由window触发, window触发的普通函数, 其中的this是undefined */}
{/* 通过bind(): 为show函数指定 this 的执行为当前对象 */}
<button onClick={this.show.bind(this)}>普通函数</button>
<button onClick={this.jiantou}>箭头函数</button>
{/* 解决方案: 利用箭头函数 调用 普通函数 */}
{/* 原格式: ()=>{ return this.show(); } */}
{/* 语法糖: ()=>{return xxx;} 则可以简化: ()=>xxx */}
{/* 点击之后: 先执行 箭头函数, 箭头函数再执行内部的 this.show(); 由于箭头函数自带 this指向保持, 所以 this.show() 中的this 就是当前App */}
<button onClick={() => this.show()}>箭头+普通</button>
</div>
);
}
}
事件传参
// 事件传参
import React, { Component } from "react";
export default class App extends Component {
show(name) {
console.log(name);
}
render() {
return (
<div>
<h3>TA最喜欢:</h3>
{/* 错误写法: {}中代码会自动执行, show方法的返回值是 空, 点击后执行的是空 */}
<button onClick={this.show("胡了哥")}>胡了哥</button>
{/* 正确: 利用bind 提前绑定好参数, 但是不会执行; 参数1 要求必须是this */}
<button onClick={this.show.bind(this, "彭小鱼晏")}>彭小鱼晏</button>
{/* 正确: 点击后执行箭头, 箭头再执行内部的 show 方法 */}
<button onClick={() => this.show("吴燕祖")}>吴燕祖</button>
</div>
);
}
}
状态值
// 状态值
// 微信小程序 借鉴了 React 的状态值写法: data setData()
// setData 在更新 data 中数据的同时, 会刷新界面
// React中: state 和 setState()
import React, { Component } from "react";
export default class App extends Component {
state = { num: 1 };
// 非官方推荐 但是使用较多: 单纯使用 setState() 的刷新效果
count = 100;
changeCount = () => {
this.count++;
// 单纯刷新界面: 利用 setState 刷新界面的特性. 参数必须传递
this.setState({}); //快捷 sst
};
doAdd() {
// this.state.num++;
// console.log(this.state.num);
// 必须使用 setState 方法才能刷新界面
this.setState({ num: this.state.num + 1 });
}
render() {
return (
<div>
<button onClick={this.doAdd.bind(this)}>{this.state.num}</button>
{/* 箭头函数, 不需要bind */}
<button onClick={this.changeCount}>{this.count}</button>
</div>
);
}
}
setState() 的异步性
// setState() 的异步性
import React, { Component } from "react";
export default class App extends Component {
state = { num: 1 };
doAdd = () => {
// 把 xxx 数据 更新到界面上 : 此操作较慢
// 为了防止此慢速操作影响到下方代码执行, 所以被放在了其它 线程中
// setState(要更新的值, 更新完毕的回调函数)
this.setState({ num: this.state.num + 1 }, () => {
console.log("num更新完毕, 值:", this.state.num);
});
// 立刻让打印 num 的值: 由于速度快, 界面还没更新完, 所以出现的是之前的值
console.log(this.state.num);
};
render() {
return (
<div>
<button onClick={this.doAdd}>{this.state.num}</button>
</div>
);
}
}
双向数据绑定
// 双向数据绑定: vue提供了v-model语法糖, 快速实现双向绑定; 但是React未提供, 只能自己来实现双向绑定效果
import React, { Component } from "react";
export default class App extends Component {
state = { word: "123456" };
// 细节: 习惯上和 组件的事件绑定的方法, 以 _ 开头
_wordChanged = (e) => {
// 事件触发的函数, 默认接收事件对象作为 最后一个参数
console.log(e.target.value);
this.setState({ word: e.target.value });
};
render() {
return (
<div>
{/*
方向1: 把值传递给页面
方向2: 页面变化时(单选框,输入框,多选..), 反向修改绑定的数据
*/}
<input
type="text"
value={this.state.word}
onChange={this._wordChanged}
/>
<br />
<div>{this.state.word}</div>
{/* 双向绑定的事件比较简单, 可以直接写在 JSX 里 */}
<input
type="text"
value={this.state.word}
onChange={(e) => this.setState({ word: e.target.value })}
/>
</div>
);
}
}
动态样式
// 动态样式
import React, { Component } from "react";
export default class App extends Component {
// 变化的值: 用 state 状态值
state = { fs: 16 };
_doAdd() {
this.setState({ fs: this.state.fs + 10 });
}
render() {
return (
<div>
<button onClick={() => this._doAdd()}>变大</button>
{/* 单位默认 px, 可以省略 */}
<div style={{ fontSize: this.state.fs }}>Hello World!</div>
</div>
);
}
}
外部样式 css文件
// 外部样式 css文件
import React, { Component } from "react";
/**
* 引入 App.css 文件
*
* html: <link ...
* css: @import url(....)
* js: import '...css';
*
* 注意: import 是用来引入模块的, 如果引入文件则必须带 路径前缀: ./ ../ /
*/
import "./App.css";
export default class App extends Component {
render() {
return (
<div>
<div className="btn">普通按钮</div>
<div className="btn default">默认按钮</div>
<div className="btn success">success</div>
</div>
);
}
}
.btn {
display: inline-block;
border-radius: 4px;
padding: 3px 10px;
font-size: 18px;
}
.btn.default {
background-color: #9cc;
color: white;
}
.btn.success {
background-color: green;
color: white;
}
计数器
// 计数器(小练习)
import React, { Component } from "react";
export default class App extends Component {
// 如果页面上某个数据 会变化: 考虑状态值state
state = { count: 5 };
// _ : 习惯上, 与事件绑定的方法, 添加 _ 前缀
// 普通函数 & 事件触发 & 严格模式 : 函数中的this指向undefined
_doAdd() {
// 更新数据 && 更新界面 : setState()
// 假设 count = 5; {count: count++} 其值为 {count: 5}
this.setState({ count: this.state.count + 1 });
}
// 箭头函数: 自带this保持, 永远不变
_doMinuse = () => {
this.setState({ count: this.state.count - 1 });
};
render() {
return (
<div>
<div>
<button onClick={this._doMinuse} disabled={this.state.count === 1}>
-
</button>
<span>{this.state.count}</span>
<button onClick={this._doAdd.bind(this)}>+</button>
</div>
</div>
);
}
}
购物车的cell
// 购物车的cell(小练习)
import React, { Component } from "react";
// 引入外部css: 必须加路径标识
import "./App.css";
export default class App extends Component {
//变化值: 放state
state = { price: 6666, count: 4, new_price: 6666 };
render() {
const { price, count } = this.state;
return (
<div>
<div className="work2 cell">
<span>iPhone</span>
<span>¥{price}</span>
<div>
<button
onClick={() => this.setState({ count: count - 1 })}
disabled={count == 1}
>
-
</button>
<span>{count}</span>
<button onClick={() => this.setState({ count: count + 1 })}>
+
</button>
</div>
<span>总价: ¥{price * count}</span>
</div>
<div>
<input
type="text"
value={this.state.new_price}
onChange={(e) => this.setState({ new_price: e.target.value })}
/>
<button
onClick={() => this.setState({ price: this.state.new_price })}
>
确定修改
</button>
</div>
</div>
);
}
}
变圆变方
// 变圆变方(小练习)
import React, { Component } from "react";
export default class App extends Component {
state = { radius: 0 };
// 状态值: 是圆 还是 方, 此值不需要显示在页面上; 可以不放在state里
status = 0; //设定: 0 变圆 1 变方
_doChanged = () => {
// setState(变更的值, 变更后回调方法)
this.setState(
{ radius: this.state.radius + [10, -10][this.status] },
() => {
// 0 -> 50: 圆形, 该变成方形
if (this.state.radius == 50) this.status = 1;
// 50 -> 0: 方形, 该变成圆形
if (this.state.radius == 0) this.status = 0;
this.setState({}); //刷新页面
}
);
};
render() {
return (
<div>
<button onClick={this._doChanged}>
{/* [][] :此写法 一定是 数组[序号] */}
{["变圆", "变方"][this.status]}
</button>
<div
style={{
width: 100,
height: 100,
backgroundColor: "red",
borderRadius: this.state.radius,
}}
></div>
</div>
);
}
}
条件渲染
// 条件渲染
// 对应 v-if
import React, { Component } from "react";
export default class App extends Component {
state = { item: 0 };
// React 的 条件渲染: 依赖于原生 if 判断语法
showDetail() {
const item = this.state.item;
if (item == 0) return <div>Lena Show</div>;
if (item == 1) return <div>Nancy Show</div>;
if (item == 2) return <div>Miya Show</div>;
}
render() {
return (
<div>
<div>
<button onClick={() => this.setState({ item: 0 })}>Lena</button>
<button onClick={() => this.setState({ item: 1 })}>Nancy</button>
<button onClick={() => this.setState({ item: 2 })}>Miya</button>
</div>
{/* 事件触发的函数, {} 中不要写() 会直接调用 */}
{/* 此处非事件, 就是希望页面显示时直接触发, 所以必须加() */}
{/* 非事件触发的函数, 不需要考虑 this 指向 */}
<div>{this.showDetail()}</div>
</div>
);
}
}
列表渲染
// 列表渲染: v-for
import React, { Component } from "react";
export default class App extends Component {
skills = ["html", "css", "js", "express", "vue"];
emps = [
{ name: "Lena", age: 19, gender: 0 },
{ name: "Nancy", age: 20, gender: 0 },
{ name: "MiYa", age: 18, gender: 0 },
{ name: "Lucy", age: 21, gender: 0 },
];
// 把数组中每个元素放在按钮中
showKills() {
let arr = [];
// 遍历数组中每个元素, 放到button中, 然后保存到新的数组里
this.skills.forEach((item, index) => {
// JSX代码放数组里, 要求每一项都有 唯一标识 key
const a = <button key={index}>{item}</button>;
arr.push(a);
});
// arr中 都是按钮
return arr;
}
showEmps() {
let arr = [];
this.emps.forEach((item, index) => {
arr.push(
<tr key={index}>
<td>{index + 1}</td>
<td>{item.name}</td>
<td>{item.age}</td>
<td>{["女", "男"][item.gender]}</td>
</tr>
);
});
return arr;
}
render() {
return (
<div>
{/* 数组内容会自动挨个显示出来 */}
<div>{this.skills}</div>
<div>{this.showKills()}</div>
<table border="1">
<thead>
<tr>
<td>序号</td>
<td>姓名</td>
<td>年龄</td>
<td>性别</td>
</tr>
</thead>
<tbody>{this.showEmps()}</tbody>
</table>
</div>
);
}
}
列表渲染 map方法实现
// map方法实现 列表渲染
import React, { Component } from "react";
export default class App extends Component {
skills = ["vue", "react", "angular", "echars", "dom"];
showSkills = () =>
this.skills.map((item, index) => <button key={index}>{item}</button>);
// 箭头函数语法糖: ()=>{return xxx;} 简化为: ()=>xxx
showSkills_3() {
return this.skills.map((item, index) => (
<button key={index}>{item}</button>
));
}
showSkills_2() {
return this.skills.map((item, index) => {
return <button key={index}>{item}</button>;
});
}
showSkills_1() {
const arr = this.skills.map((item, index) => {
return <button key={index}>{item}</button>;
});
return arr;
}
render() {
return (
<div>
<div>{this.showSkills()}</div>
</div>
);
}
}
生命周期
// 生命周期
import React, { Component } from "react";
export default class App extends Component {
state = { show: false, name: "Lena" };
render() {
return (
<div>
<button onClick={() => this.setState({ show: !this.state.show })}>
显示/隐藏
</button>
<button onClick={() => this.setState({ name: this.state.name + "Lena" })}>
变更name
</button>
{this.state.show ? <Son name={this.state.name} /> : null}
</div>
);
}
}
class Son extends Component {
// 钩子函数: 生命周期在不同解决触发 固定的方法
componentDidMount() {
//挂载完毕
console.log("componentDidMount: 组件挂载完毕");
}
// 通过返回值, 决定页面要不要刷新
// 参数为想要更新的结果
// 实际应用中: 可以利用一些 diff 算法 避免一些不必要的刷新.
shouldComponentUpdate(props, state) {
console.log("shouldComponentUpdate: 是否要更新页面?");
console.log("更新后props:", props);
console.log("更新后state:", state);
// 希望不显示偶数
if (state.num % 2 == 0) return false; //false不刷新
return true; //true 刷新 , 执行 render() 方法
}
// 参数时 更新前的值
componentDidUpdate(props, state) {
// props 是外来属性传参
console.log("更新前props:", props);
console.log("更新后props:", this.props);
console.log("更新前state:", state);
console.log("更新后state:", this.state);
}
componentWillUnmount() {
//将要卸载
console.log("componentWillUnmount: 组件将要卸载");
}
state = { num: 1 };
render() {
// let num = this.state.num;
return (
<div>
<h4>Son组件</h4>
<button onClick={() => this.setState({ num: this.state.num + 1 })}>
{this.state.num}
</button>
<div>{this.props.name}</div>
</div>
);
}
}
网络请求
// 网络请求
import React, { Component } from "react";
import "./App.css";
export default class App extends Component {
// 会变化 && 放在页面上的 都放state
state = { result: [] };
componentDidMount() {
const url = "https://api.apiopen.top/getWangYiNews";
// axios: this.axios.get(url).then(res=>{})
// react
fetch(url)
.then((res) => res.json())
.then((res) => {
console.log(res);
this.setState({ result: res.result });
});
}
showNews = () =>
this.state.result.map((item, index) => (
<div key={index} className="news cell">
<img src={item.image} alt="" />
<div>
<div>{item.title}</div>
<div>{item.passtime}</div>
</div>
</div>
));
render() {
return <div>{this.showNews()}</div>;
}
}
网络请求(跨域)
// 网络请求(跨域)
import React, { Component } from "react";
import "./App.css";
export default class App extends Component {
// 关于请求返回值的默认值: 数组=[] 对象=null
state = { data: null };
componentDidMount() {
// const url = "https://m.douyu.com/api/room/list?page=1&type=yz";
const url = "/douyu/api/room/list?page=1&type=yz";
fetch(url)
.then((res) => res.json())
.then((res) => {
console.log(res);
this.setState({ data: res.data });
});
}
showRooms = () =>
this.state.data.list.map((item, index) => (
<div key={index} className="douyu cell">
<div>
<img src={item.roomSrc} alt="" />
<div>{item.hn}</div>
<div>{item.nickname}</div>
</div>
<div>{item.roomName}</div>
</div>
));
render() {
// 如果页面数据是网络请求获取的, 应该判断是否存在
if (!this.state.data) return <div></div>;
return (
<div
style={{
// width: 630,
display: "flex",
flexWrap: "wrap",
}}
>
{this.showRooms()}
</div>
);
}
}
// setupProxy.js
// 接上
// 创建配置文件, 在 src 目录下: setupProxy.js
// src/setupProxy.js
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = function (app) {
app.use(
createProxyMiddleware("/douyu", {
target: "https://m.douyu.com", //转发到的域名
changeOrigin: true, // 域名有变化
secure: true, // https 请求
pathRewrite: {
"^/douyu": "", // 接口地址中开头的 /douyu 重置为空
},
})
);
// 如果有更多的跨域, 就在下方继续写 app.user()...
};
路由系统
import React, { Component } from "react";
// 引入路由相关的所有 模块
import {
Link, //相当于 router-link, 制作跳转超链接
Switch, //相当于 router-view, 负责根据路径切换组件
BrowserRouter as Router, // 相当于 router, 管理整个路由系统
Route, // 管理 路径 和 组件的对应关系
} from "react-router-dom";
// 引入其它的组件
import About from "./pages/About";
import Home from "./pages/Home";
import News from "./pages/News";
import Contact from "./pages/Contact";
export default class App extends Component {
render() {
// 最外层标签写 Router, 这样内容就被路由管理
return (
<Router>
<div style={{ border: "1px solid purple" }}>
<Link to="/home">Home</Link>
<br />
<Link to="/news/lena/1">News:lena</Link>
<br />
<Link to="/news/nancy/2">News:nancy</Link>
<br />
<Link to="/about">About</Link>
<br />
<Link to="/contact">Contact</Link>
</div>
<div>
{/* Switch: 类比 router-view, 用于切换组件 */}
<Switch>
{/* 路径的模糊匹配: 默认是模糊匹配, 此处就是 / 开头都匹配成功 */}
{/* exact: 精确匹配 */}
<Route path="/" exact component={Home} />
<Route path="/home" component={Home} />
{/* 利用 : 来代表参数, 同 vue */}
<Route path="/news/:title/:id" component={News} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
</Switch>
</div>
</Router>
);
}
}
路由参数
// src/pages/News
import React, { Component } from "react";
import { withRouter } from "react-router-dom";
class News extends Component {
render() {
console.log(this.props);
const { id, title } = this.props.match.params;
/**
* vue中:
* this.$route : 存储路由相关值
* this.$router: 存储操作路由的相关函数
*/
return (
<div>
<h2>News页面</h2>
<div>
{id}, {title}
</div>
<button onClick={() => this.props.history.push("/home")}>
前往Home页面
</button>
</div>
);
}
}
// withRouter: 向组件 News 的属性 props 中注入路由相关代码
export default withRouter(News);
Redux
// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
// Provider: 专门用于 集成store到React
import { Provider } from "react-redux";
// Redux管理的store
import store from "./store";
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
/**
* Redux: 符合 Flux 思想的一种具体实现的框架
* - 多组件之间共享的数据 要统一管理
* - 统一管理的数据 只暴露一种修改方式
* - 可以监听数据变化
*/
import { createStore } from "redux";
//1. 创建state, 存储管理的变量
const initState = { count: 1 };
//2. reducer: 就是VueX的mutations, 用来存储修改 state 值的相关方法
const reducer = (state = initState, action) => {
switch (action.type) {
case "increment":
// 固定写法: {原值, 新值}
return { ...state, count: state.count + 1 };
case "minuse":
return { ...state, count: state.count - 1 };
default:
return state;
}
};
// 3. reducer 函数, 用 store 进行管理
const store = createStore(reducer);
// 4. 读取 state 的方法
// VueX: this.$store.state.count
// React
console.log(store.getState()); //读取的 reducer 的 default 返回值
// 5. 监听属性变更:
store.subscribe(() => {
console.log("state有变化:", store.getState());
});
// 6. 调用修改属性的方法
// VueX: this.$store.commit('increment')
// React
store.dispatch({ type: "increment" });
// -1
store.dispatch({ type: "minuse" });
// 带参数
import { createStore } from "redux";
const initState = {
num: 100,
skills: ["vue", "react"],
age: 19,
};
const reducer = (state = initState, action) => {
switch (action.type) {
case "add":
return { ...state, num: state.num + 1 };
case "jian":
return { ...state, num: state.num - 1 };
case "skills_add":
const skill = action.skill;
return { ...state, skills: [...state.skills, skill] };
case "age_update":
return { ...state, age: action.new_age };
default:
return state;
}
};
const store = createStore(reducer);
store.subscribe(() => {
console.log("变更:", store.getState());
});
store.dispatch({ type: "add" });
store.dispatch({ type: "jian" });
// 向 skills 中添加新的项目
store.dispatch({ type: "skills_add", skill: "Angular" });
store.dispatch({ type: "skills_add", skill: "DOM" });
store.dispatch({ type: "skills_add", skill: "Bootstrap" });
// 更新年龄
store.dispatch({ type: "age_update", new_age: 29 });
Hook
// rfc
// 在函数中 利用 Hook 特性, 实现状态值 和 生命周期
import React, { useState, useEffect } from "react";
export default function App() {
// 相当于: state={num:1}
const [num, setNum] = useState(1);
// setNum 只能用于更新 num 变量, 属于配对使用
const [count, setCount] = useState(500);
const [show, setShow] = useState(false);
return (
<div>
<button onClick={() => setNum(num + 1)}>{num}</button>
<br />
<button onClick={() => setCount(count + 2)}>count:{count}</button>
<hr />
<button onClick={() => setShow(!show)}>显示/隐藏</button>
{show ? <Son /> : null}
</div>
);
}
//子
function Son() {
const [num, setNum] = useState(1);
const [count, setCount] = useState(1);
//Hook: 生命周期
useEffect(() => {
// 此处监听 挂载完毕 和 更新完毕
console.log("组件挂载完毕 或 更新完毕");
return () => {
// 此处监听 卸载时
console.log("组件将要卸载");
};
}, [num]); //参数2数组: 哪些属性变化时 能够触发生命周期
return (
<div style={{ border: "2px solid red" }}>
<h2>子元素</h2>
{/* 页面有变化时, 会触发 卸载 -> 更新 */}
<button onClick={() => setNum(num + 1)}>num:{num}</button>
<button onClick={() => setCount(count + 1)}>count:{count}</button>
</div>
);
}