什么是虚拟 DOM
虚拟DOM是:用JS来模拟一颗 DOM 树,放在浏览器内存中
当你要改变时,虚拟 DOM 使用 diff 算法进行
新旧虚拟 dom 的比较将修改了的更新到实际的 DOM 树,减少了 DOM 操作
什么是 diff 算法
计算出虚拟 DOM 中真正变化的部分,并且只针对该部分进行 DOM 更新,而非重新渲染整个页面
react 的使用
安装 cnpm i react react-dom -S 安装包
- react: 专门用于创建组件和虚拟 DOM 的,同时组件的生命周期都在这个包中
- react-dom: 专门进行 DOM 操作的,最主要的应用场景,就是 ReactDOM.render()
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>react基本使用</title>
</head>
<body>
<!-- 创建一个div,虚拟DOM会渲染到这个div里 -->
<div id="app"></div>
<!-- 浏览器不支持 import -->
<script src="../build/build.js"></script>
<!-- 这里是打包前的代码 -->
<script>
import React from "react"; // 创建组件,虚拟 DOM 元素,生命周期
import ReactDOM from "react-dom"; // 把创建好的虚拟 DOM 放到页面上展示
// 创建虚拟 DOM
// 参数 1: 创建元素类型
// 参数 2: 是一个对象或 null,表示当前这个 DOM 元素的属性
// 参数 3: 子节点 (文本节点)
// 参数 4: 其他子节点
let myh1 = React.createElement(
"h1",
{ id: "myh1", title: "我是标签" },
"这是子节点"
);
// 把创建好的虚拟 DOM 渲染到页面上
// 参数 1: 要渲染的那个虚拟 DOM
// 参数 2: 指定页面上渲染的位置 (类似于Vue中的这个 <div id="app"></div>)
ReactDOM.render(myh1, document.getElementById("app"));
</script>
</body>
</html>
JSX 语法
- JSX是一种JavaScript的语法扩展(eXtension ),也在很多地方称之为JavaScript XML,因为看起就是一段XML语法;
- 在 JSX 中的元素添加 class 类名需;要使用 className 来替代 class; htmlFor 替换 label 的 for 属性
- 正常的 HTML 元素是小写,如果是大写,默认认为是组件
import React from "react";
import ReactDOM from "react-dom";
// 直接运行会报错,要安装 babel 转换成 React.createElement 形式的语法
// 直接写JSXl语法,然后 babel 内部会转成 React.createElement 这种形式
let myh1 = (
<div id="mydiv" title="aaa">
这个一个DIV元素
</div>
);
ReactDOM.render(myh1, document.getElementById("app"));
JSX 中使用变量
- 通过 {} 语法,Vue 中是{{}}
import React from "react";
import ReactDOM from "react-dom";
let num = 50;
let bool = true;
let title = "可变的title";
let arr = ["钢铁侠", "蜘蛛侠", "雷神", "死特", "美国队长"];
let myh1 = (
<div id="mydiv" title="aaa">
{num}
<hr />
{bool ? "条件为真" : "条件为假"}
<hr />
<p title={title}>{title}</p>
<hr />
{arr.map((item) => (
<h3 key={item}>{item}</h3>
))}
</div>
);
// 为啥用 map,map有返回值,会生成一个新数组
ReactDOM.render(myh1, document.getElementById("app"));
react 中使用组件
- 将页面拆分成一个个小的、可复用的组件。
- 这样让我们的代码更加方便组织和管理,并且扩展性也更强。
import React from "react";
import ReactDOM from "react-dom";
function Hello() {
return <div>这是Hello组件</div>;
}
ReactDOM.render(
<div>
<Hello></Hello>
</div>,
document.getElementById("app")
);
react 组件传参
import React from "react";
import ReactDOM from "react-dom";
function Hello(props) {
console.log(props);
return (
<div>
这是Hello组件,
<div>
这是传过来的数据 ↓<p>名字:{props.send.name}</p>
<p>ID:{props.send.id}</p>
<p>颜色:{props.send.color}</p>
</div>
</div>
);
}
let Data = {
id: 0,
name: "数据0",
color: "red",
};
ReactDOM.render(
<div>
<Hello send={Data}></Hello>
</div>,
document.getElementById("app")
);
子传父
// components 文件夹下的,父组件代码
import React from "react";
import Child from "./Child.jsx"
export default class Father extends React.Component{
constructor(){
super()
this.state = {
data:"父组件内容"
}
}
render(){
return (
<div>
<h1>父组件---{this.state.data}</h1>
<Child ParentMethod={ this.FatherClick }></Child>
</div>
)
}
// 父组件方法,传给子组件,子组件通过参数进行修改
FatherClick = (canshu) =>{
this.setState({
data:canshu
})
}
}
// components 文件夹下的,子组件代码
import React from "react";
export default class Child extends React.Component{
render(){
this.state = {
Data:"子组件内容"
}
return (
<div>
<h1>子组件</h1>
<button onClick={ ()=> this.ChildClick() }>改变父组件内容</button>
</div>
)
}
ChildClick(){
let {Data} = this.state
this.props.ParentMethod(Data)
}
}
// 入口文件
import React from "react";
import ReactDOM from "react-dom";
import Father from "./components/Father.jsx"
ReactDOM.render(<Father />, document.getElementById("app"));
兄弟传参
import React from "react";
import ReactDOM from "react-dom";
// 兄弟A组件
class BrotherA extends React.Component {
constructor() {
super();
this.state = {
data: "传给B组件的参数值",
};
}
render() {
return (
<div>
<h1>兄弟A组件</h1>
<button onClick={() => this.chuancan()}>按钮A</button>
</div>
);
}
chuancan = () => {
let { data } = this.state;
this.props.change(data);
};
}
// 兄弟B组件
class BrotherB extends React.Component {
render() {
return (
<div>
<h1>兄弟B组件</h1>
A组件的参数:{this.props.CommonData}
</div>
);
}
}
// 共同的父亲
class Father extends React.Component {
constructor() {
super();
this.state = {
CommonData: "",
};
}
render() {
return (
<div>
<BrotherA change={this.CommonFunction} />
<BrotherB {...this.state} />
</div>
);
}
// 跟子传父一样,兄弟A组件把参数给父组件,然后父组件传给兄弟B组件
CommonFunction = (canshu) => {
this.setState({
CommonData: canshu,
});
};
}
ReactDOM.render(<Father />, document.getElementById("app"));
将组件抽离为单独的.jsx 文件
// components中的 Hello.jsx文件
import React from "react"; // 你要创建组件必须依赖React包
export default function Hello(props) {
console.log(props);
return (
<div>
这是Hello组件,
<div>
这是传过来的数据 ↓<p>名字:{props.send.name}</p>
<p>ID:{props.send.id}</p>
<p>颜色:{props.send.color}</p>
</div>
</div>
);
}
// index.js文件
import React from "react";
import ReactDOM from "react-dom";
import Hello from "./components/Hello.jsx"; // 不能省略后缀名
let Data = {
id: 0,
name: "数据0",
color: "red",
};
ReactDOM.render(
<div>
<Hello send={Data}></Hello>
</div>,
document.getElementById("app")
);
使用 class 创建组件
- 注意:使用 class 关键字创建的组件,有自己的私有数据和生命周期函数;
- 注意:使用 function 创建的组件,只有 props,没有自己的私有数据和生命周期函数和this;
- 函数组件调用完就会销毁
import React from "react";
import ReactDOM from "react-dom";
class Movie extends React.Component {
// render函数的作用: 渲染当前组件的 JSX 元素
constructor() {
// 子类继承父类则,必须调用 super(),语法规范
// super()代表父类的构造器(constructor)
super();
// 语法规范:在子类中, this 只能放到 super() 之后使用
this.data = {
msg: "我是Movie组件的私有数据",
};
}
// 在 class 关键字创建的组件中,如果想使用外界传递过来的 props参数,不需接收,直接通过 this.props .***访问
render() {
return (
<div>
这是Movie组件---{this.props.send.name}---{this.data.msg}
</div>
);
}
}
let Data = {
id: 0,
name: "数据",
color: "red",
};
ReactDOM.render(<Movie send={Data}></Movie>, document.getElementById("app"));
react 使用行内样式
import React from "react";
import ReactDOM from "react-dom";
function Style() {
return <div style={{ color: "red", fontSize: "50px" }}>变红</div>;
}
ReactDOM.render(<Style></Style>, document.getElementById("app"));
react 使用样式表
import React from "react";
import ReactDOM from "react-dom";
import css from "./css/index.module.css";
function Style() {
return <div className={css.test}>样式表样式</div>;
}
ReactDOM.render(<Style></Style>, document.getElementById("app"));
- css 文件
/* index.module.css 记得加 module */
.test {
color: rebeccapurple;
font-size: 50px;
}
react 事件使用
import React from "react";
import ReactDOM from "react-dom";
class My extends React.Component {
constructor() {
super();
// this.state,只能这样写,否则赋值有问题
this.state = {
msg:"默认值"
}
}
render() {
return <div>
{/* 三种使用方式 , 最后一种最常用 */}
{/* 第一种 */}
{/* <button onClick = { this.btn.bind(this) }>按钮</button> */}
{ this.state.msg }
{/* 第三种 */}
<button onClick = { () => this.btn("哈哈哈") }> 按钮 </button>
</div>
}
// 第二种
btn = (face) => {
// this.data.msg = "" 这种赋值方式,页面不会更新
// this.setState 方法的执行,是异步的,如果想拿到更新后的值通过 callback
// this.setState({ msg:face }, function (){ console.log(this.state.msg) })
this.setState({ msg:face },function (){ console.log(this.state.msg) })
}
}
ReactDOM.render(<My></My>, document.getElementById("app"));
setState
- setState 设计为异步,可以显著的提升性能;
- 如果每次调用 setState 都进行一次更新,那么意味着 render 函数会被频繁调用,界面重新渲染,这样效率是很低的;
- 最好的办法应该是获取到多个更新,之后进行批量更新;
- 如果同步更新了state,但是还没有执行 render 函数,那么 state 和 props 不能保持同步
- state 和 props 不能保持一致性,会在开发中产生很多的问题;
setState 数据合并
import React from "react";
import ReactDOM from "react-dom";
class Tools extends React.Component {
constructor() {
super();
this.state = {
content: "Hello world",
age: 18,
};
}
render() {
return (
<div>
{/* 这里改成了 "哈哈哈哈" 很显然并没有覆盖 age 属性 */}
{this.state.content}
<p> {this.state.age}</p>
<button onClick={(e) => this.change()}> 改变文字 </button>
</div>
);
}
change() {
this.setState({
content: "哈哈哈哈",
});
// 因为 React 内部有做一件事
// Object.assign({}, this.state, { content: "哈哈哈哈" })
// 所以不用担心会覆盖 age 属性
}
}
ReactDOM.render(<Tools />, document.getElementById("app"));
const state = { content: "Hello world", age: 18 };
const ChangeState = { content: "哈哈哈哈" };
let result = Object.assign({}, state, ChangeState);
console.log(result);
// { content: '哈哈哈哈', age: 18 }
Ref
import React, { Component, createRef } from "react";
import ReactDOM from "react-dom";
class Ref extends Component {
constructor() {
super();
this.createRef = createRef();
this.titleEl = "";
}
render() {
return (
<div>
{/* 字符串形式 */}
<h2 ref="titleRef">Hello React</h2>
{/* 对象形式,推荐 */}
<h2 ref={this.createRef}>Hello React</h2>
{/* 函数形式 */}
<h2 ref={(arg) => (this.titleEl = arg)}>Hello React</h2>
<button onClick={(e) => this.changeText()}>改变文本</button>
</div>
);
}
changeText() {
// 方式一: 不推荐
this.refs.titleRef.innerHTML = "Hello Coder";
// 方式二
this.createRef.current.innerHTML = "Hello JavaScript";
// 方式三
this.titleEl.innerHTML = "Hello MySQL";
}
}
ReactDOM.render(<Ref />, document.getElementById("app"));
Ref获取子组件数据
import React from "react";
import ReactDOM from "react-dom";
class Father extends React.Component {
componentDidMount(){
console.log(this.currentDOM.state.data);
}
render() {
return (
<div>
<h1>父组件</h1>
<Child ref={(ChildDOM) => { this.currentDOM = ChildDOM}}></Child>
</div>
);
}
}
class Child extends React.Component {
constructor(){
super()
this.state = {
data:"子组件数据"
}
}
render() {
return (
<div>
<h1>子组件</h1>
</div>
);
}
}
ReactDOM.render(<Father />, document.getElementById("app"));
react 实现双向数据绑定
import React from "react";
import ReactDOM from "react-dom";
class My extends React.Component {
constructor() {
super();
this.state = {
msg: "默认值",
};
}
render() {
return (
<div>
{/*
如果只是把,文本框的 value 属性,绑定到了 state 状态,但是如果不提供 onChange 处理函数的话,
得到的文本框,将会是一个只读的文本框
要么提供一个 readOnly (只读文本框),要么提供一个 onChange (监听状态发生改变) 函数
*/}
{/* (1) <input value={this.state.msg} onChange={(e) => this.textChange(e)} /> */}
<input value={this.state.msg} onChange={() => this.textChange()} ref="txt" />
{this.state.msg}
<button onClick={() => this.btn("哈哈哈")}> 按钮 </button>
</div>
);
}
textChange = (e) => {
// 方法 (1) 通过事件对象
// let newvalue = e.target.value;
// this.setState({
// msg: newvalue,
// });
// 方法 (2) 通过 refs
let newvalue = this.refs.txt.value
this.setState({
msg: newvalue,
});
};
btn = (face) => {
this.setState({ msg: face }, function () {
console.log(this.state.msg);
});
};
}
ReactDOM.render(<My></My>, document.getElementById("app"));
react 生命周期
- 什么是生命周期,就是组件从创建,运行,到销毁的过程叫做生命周期
componentWillMount // 组件将要渲染
componentDidMount // 组件渲染完成
componentWillReceiveProps // 组件将要接受 porps 数据
shouldComponentUpdate(newProps,newState) // 组件收到新的 state 或 porps 判断是否更新,返回布尔值,返回 true 则更新
componentWillUpdate // 组件将要更新
componentDidUpdate // 组件更新完成
componentWillUnMount // 组件将要卸载
React 性能优化
import React, { PureComponent, memo } from "react";
import ReactDOM from "react-dom";
// Header
// memo 返回一个新的组件
const NewHeader = memo(function Header() {
console.log("Header被调用");
return <h2>我是Header组件</h2>;
});
// Main
class Banner extends PureComponent {
render() {
console.log("Banner render函数被调用");
return <h3>我是Banner组件</h3>;
}
}
function ProductList() {
console.log("ProductList被调用");
return (
<ul>
<li>商品列表1</li>
<li>商品列表2</li>
<li>商品列表3</li>
<li>商品列表4</li>
<li>商品列表5</li>
</ul>
);
}
class Main extends PureComponent {
render() {
console.log("Main render函数被调用");
return (
<div>
<Banner />
<ProductList />
</div>
);
}
}
// Footer
const Footer = memo(function Footer() {
console.log("Footer被调用");
return <h2>我是Footer组件</h2>;
});
export default class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: 0,
};
}
render() {
console.log("App render函数被调用");
return (
<div>
<h2>当前计数: {this.state.counter}</h2>
<button onClick={(e) => this.increment()}>+1</button>
<NewHeader />
<Main />
<Footer />
</div>
);
}
increment() {
this.setState({
counter: this.state.counter + 1,
});
}
}
/*
当我们更新 APP 组件的值时, Header 这些组件也被调用了 render 函数;
这样是非常浪费性能的,所以把 Component 改成 PureComponent
Component:
App render函数被调用
Header被调用
Main render函数被调用
Banner render函数被调用
ProductList被调用
Footer被调用
PureComponent 的作用:
PureComponent 会对 props 和 state 做浅层比较,看看有没有改变
有改变调用 shouldComponentUpdate() { return true }
shouldComponentUpdate(newPorps,newState) 作用:
组件收到新的 props 或 state 判断是否更新,返回布尔值,返回 true 则更新
PureComponent:
App render函数被调用
Header被调用
Footer被调用
但我们发现 函数组件还是调用了 render 函数,所以我们可以使用 memo
memo 跟 PureComponent 一样也会做浅层比较
PureComponent + memo:
App render函数被调用
*/
ReactDOM.render(<App />, document.getElementById("app"));
安装 React 脚手架
- 不要有中文
- cnpm install -g create-react-app(安装过这个就直接进行第二步)
- create-react-app hellowo(项目名)
- cd hellowo
- 运行命令 npm start
总结︰脚手架让项目从搭建到开发,再到部署,整个流程变得快速和便捷的工具
react 插条
import React from "react";
import ReactDOM from "react-dom";
// 默认会有一个 constructor
class Movie extends React.Component {
render() {
return (
<div>
组件插条
{this.props.children}
</div>
);
}
}
ReactDOM.render(
<Movie>
<h1>子组件</h1>
<h1>子组件</h1>
<h1>子组件</h1>
</Movie>,
document.getElementById("app")
);
react 高阶组件
-
什么是高阶函数:
- 至少满足以下条件之一:
- 接受一个或多个函数作为输入
- 输出一个函数
-
官方的定义:高阶组件是参数为组件,返回值为新组件的函数
- 高阶组件本身不是一个组件,而是一个函数
- 其次,这个函数的参数是一个组件,返回值也是一个组件
import React, { PureComponent } from "react";
import ReactDOM from "react-dom";
/*
*/
class App extends PureComponent {
render() {
return <div>App: {this.props.name}</div>;
}
}
function FunComponent(Component) {
class NewComponent extends PureComponent {
render() {
return <Component {...this.props} />;
}
}
NewComponent.displayName = "KOKO";
return NewComponent;
}
/* function FunComponent2(Component) {
function NewComponent(props) {
return <Component {...props}/>
}
NewComponent.displayName = "KOKO";
return NewComponent;
} */
const EnhanceComponent = FunComponent(App);
ReactDOM.render(
<EnhanceComponent name="KOKO" />,
document.getElementById("app")
);
react 路由
- 先安装路由,cnpm install react-router-dom -S
- 使用 react 脚手架,然后把下面代码复制到 App.js
- to = { {} } 一个 {} 代表变量,两个 { {} } 代表变量里是一个对象
- 路由模:
- HashRouter hash模式带#号,页面切换或刷新时,页面不会刷新
- BrowserRouter browser历史记录模式,没有#号,他是通过历史记录api来进行路由切换的,页面会刷新,木地模式不会
import React from "react";
import "./App.css";
import { BrowserRouter as Router, Link, Route,NavLink } from "react-router-dom";
function Home() {
return (
<div>
<h1>首页</h1>
</div>
);
}
function Me(props) {
console.log(props);
return (
<div>
<h1>个人中心</h1>
</div>
);
}
function Goods(props) {
console.log(props.match.params.id);
return (
<div>
<h1>商品</h1>
</div>
);
}
class Routers extends React.Component {
render() {
return (
<div id="app">
<Router> {/* Router 根,代表这是个路由 */}
<div className="nav">
<Link to="/">首页</Link> {/* Link 实现组件跳转,相当于 Vue router-link */}
<Link to={{ pathname: "/Me" ,search:"?username=adimin"}}>个人中心</Link>
<Link to="/Goods/10086">商品</Link>
{/* NavLink 可以动态的给选中的导航添加 active 的类名 */}
<NavLink to="/Goods/10086">商品</NavLink>
</div>
{/* exact 加上这个只有路由路径唯 / 才显示 Home 组件,不加 exact 只要有 / 就显示 Home 组件 */}
<Route path="/" exact component={Home}></Route>{/* Route 路径对应的组件 */}
<Route path="/Me" component={Me}></Route>
<Route path="/Goods/:id" component={Goods}></Route>
</Router>
</div>
);
}
}
export default Routers;
二级路由
import React from "react";
import { BrowserRouter as Router, Link, Route } from "react-router-dom";
function Center(props) {
console.log(props.location.params); // 跳转参数
return (
<div>
<h1>我的</h1>
<Link to="/Center/wallet">钱包</Link>
<Link to="/Center/bill">帐单</Link>
</div>
);
}
// 二级路由
function wallet() {
return <div>钱包</div>;
}
// 二级路由
function bill() {
return <div>帐单</div>;
}
class Routers extends React.Component {
render() {
return (
<Router>
<Link to={{ pathname: "/Center", params: { id: 6666666 } }}>我的</Link>
<Route path="/Center" component={Center}></Route>
<Route path="/Center/wallet" component={wallet}></Route>
<Route path="/Center/bill" component={bill}></Route>
</Router>
);
}
}
export default Routers;
Redux
-
先安装 cnpm install redux -S
-
解决React数据管理(状态管理),用于中大型,数据比较庞大,组件之间数据交互多的情况下使用
-
如果你不知道是否需要使用Redux,那么你就不需要用它!
store
- store:存放数据的仓库,获取仓库 store.getState()
- 操作仓库 store.dispatch()
action
- action:命令 Redux 执行规则,必须要包含 type 属性
reducer
- reducer:执行 action 的命令,把新的 state 发送给 store
// node 中使用
let redux = require("redux"); // 导入 redux
// React 中使用
import { createStore } from "redux";
// reducer
const InitState = {
counter: 0,
};
function reducer(state = InitState, action) {
switch (action.type) {
case "ADD":
return { ...state, counter: state.counter + 1 };
case "SUB":
return { ...state, counter: state.counter - action.sum };
}
}
// store 创建仓库,把数据放到仓库(必须是纯函数)
const Store = redux.createStore(reducer);
// const Store = createStore(reducer);
// action
const action1 = { type: "ADD" };
const action2 = { type: "SUB", sum: 5 };
// 使用 action 它会执行 reducer 函数
Store.dispatch(action1);
Store.dispatch(action2);
console.log(Store.getState().counter);
React-Hook
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
function Btn() {
/*
参数和返回值:
参数: 初始化值,如果不设置为 undefined
返回值:数组,包含两个元素
> 元素一:
当前状态的值 (第一调用为初始化值)
> 元素二∶
设置状态值的函数;
*/
const [count, setCount] = useState(0); // 初始化值
const [obj, setObj] = useState({ name: "张三" });
const [Arr, setArr] = useState(["KOKO", "KOBE", "JANl"]);
// useEffect === componentDidMount 和 componentDidUpdate 这两个生命周期
useEffect(() => {
console.log("执行了");
document.title = count;
}, [count]); // 第二个参数传入一个空数组就只会在第一次时调用;
// [count] 表示当 count 发生改变了才会调用
return (
<div>
<p>{count}</p>
<p>
{obj.name} - {obj.age}
</p>
<ul>
{Arr.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
<button onClick={() => setCount(count + 1)}>数值自增</button>
<button onClick={() => setObj({ name: "李四", age: 15 })}>
对象按钮
</button>
<button onClick={() => setArr([...Arr, "TOM"])}>增加列表</button>
</div>
);
}
ReactDOM.render(<Btn />, document.getElementById("app"));
useContext
import React, { Component, createContext, useContext } from "react";
import ReactDOM from "react-dom";
let UserContext = createContext();
let SexContext = createContext();
function Grandson() {
const userContext = useContext(UserContext);
return (
<div>
<div>Grandson组件 👇</div>
<span>用户昵称: {userContext.name}</span>
<br />
<span>年龄: {userContext.age}</span>
</div>
);
}
function Son() {
const sexContext = useContext(SexContext);
return (
<div>
<div>Son组件</div>
<div>性别: {sexContext.sex}</div>
<Grandson />
</div>
);
}
class APP extends Component {
constructor(props) {
super(props);
this.state = {
name: "KOKO",
age: 20,
};
}
render() {
return (
<div>
<UserContext.Provider value={this.state}>
<SexContext.Provider value={{ sex: "男" }}>
<div>APP组件</div>
<Son />
</SexContext.Provider>
</UserContext.Provider>
</div>
);
}
}
ReactDOM.render(<APP />, document.getElementById("app"));