文章目录
一、React 起步
<div id="app"></div>
<!-- react 核心库 -->
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<!-- react提供的与dom相关功能的库 -->
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 主要是将es6代码转为es5,并提供jsx语法支持 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<!-- 设置脚本使用babel -->
<script type="text/babel">
console.log(ReactDOM); // react一些方法
// 主要使用render渲染一个dom组件到指定的容器里
// 第一个参数是 一个组件,第二个参数是要渲染的dom容器
ReactDOM.render(<div>Hello React</div>,document.querySelector("#app"))
// render渲染内容是不可变的,需要改变内容则要重新调用render函数渲染
setTimeout(()=>{
ReactDOM.render(<div>React change</div>,document.querySelector("#app"))
},2000)
</script>
二、jsx(核心)
- react 使用jsx代替常规的js操作
- jsx 是js的语法扩展(js中可以使用jsx语法。jsx是属于js的一部分,可以和js混合用)
- jsx 是类似xml结构(一般是有开始和结束标记。可以理解为直接在js里面可以使用html标记)
- jsx 特点:
a. jsx 执行速度很快,直接编译为js执行,运行经过优化,速度更快。
b. 类型更安全,在编译的时候可以发现基础的错误。
c. 使用jsx和和js完美融合,使用jsx描述ui更便捷
注意:
class需要使用className
style中必须使用对象设置style={ {backgroundColor:"green"} }
定义虚拟 DOM 时,不要写引号
标签中混入 JS 表达式需要使用 { }
只能有一个根标签
标签必须闭合,单标签结尾必须添加 / :<input type="text" />
标签首字母小写,则把标签转换为 HTML 对应的标签,若没有,则报错
标签首字母大写,则渲染对应组件,若没有定义组件,则报错
jsx使用
1. jsx把html标签作为一个值使用
jsx是可以把html标签作为一个值使用(普通的js里面,不能把html标签作为一个变量值)
jsx 类似于html标签,可以直接把html标签当作一个js的值使用,jsx只有一个根元素,可以嵌套多个元素
// jsx使用{/* */}注释jsx语法
const div1 = <div>
<span>hello</span>
<div>React
{/* <em>!!!!</em> */}
</div>
</div>
ReactDOM.render(div1,document.querySelector("#app")) // 挂载dom
2. jsx 可以在{}嵌入js单行表达式
// 1 { } 里面使用js变量
const num = 100
const div1 = <div>{ num }</div>
// 2 { } 进行js的运算
const div2 = <div>{ 100 * 100 + 17}</div>
// 3 { } 三元表达式
const div3 = <div>{ num >= 100? "Yes":"No" }</div>
// 4 {} 执行函数(执行函数需要添加小括号)
function demo() {
return "demo"
}
const div4 = <div>{ demo() }</div>
ReactDOM.render(div4,document.querySelector("#app")) // 挂载dom
3. jsx的style样式
style样式,react官方推荐使用style内联样式,样式用js对象方式描述
// jsx里面元素的 class属性为了和js的class关键词区分,class属性使用className
const div6 = <div className="box1 box2">这是Style渲染的内容</div>
// jsx中的html标签的属性可以正常使用,如果属性需要使用js变量,则需要用{}
let box1 = "box1"
let box2 = "box2"
// 注意:如果需要加两个样式 需要用空格隔开
const div7 = <div className={box1 +" "+box2}>div7</div>
ReactDOM.render(div7,document.querySelector("#app")) // 挂载dom
一个一个设置
import logo from './assets/react.svg'
function App() {
let logoStyle = {
width: "100vh",
height: 200,
backgroundColor: "gray" // style的样式多个单词用驼峰命名
}
return (
<div>
{/*jsx里面元素的 class属性为了和js的class关键词区分,class属性使用className */}
<img src={logo}
className='logoImg'
style={logoStyle}
/>
</div>
)
}
export default App
展开语法设置
tips:ES6 里面的展开运算符必须放在 {…}或者 […]中
import logo from './assets/react.svg'
function App() {
let logoStyle = {
className:'logoImg',
style: {
width:200,
height:200,
backgroundColor:"gray",
}
}
return (
<div>
<img src={logo}
alt=''
// 注意:这里的展开符并不是ES6里面的展开运算符,这是jsx独有的功能支持
{...logoStyle}
/>
</div>
)
}
export default App
4. jsx插入数组
jsx允许插入数组,如果数组的成员是jsx,会自动展开成员
// 1. 数组成员是jsx,可以自动展开渲染
// 批量渲染的jsx元素,必须添加key属性设置唯一值,主要是为了提高diff算法效率
const div1 = [<div key={1}>1</div>,<div key={2}>2</div>,<div key={3}>3</div>]
ReactDOM.render(div1,document.querySelector("#app")) // 挂载dom
// 2. 数组是非jsx成员,不能直接展开,需要转为jsx语法
const list = [{name:"zs",age:19},{name:"anna",age:25},{name:"kk",age:16}]
const div2 = <section>
{list.map(item=>{
return <div key={item.name}>{item.name}-{item.age}</div>
})}
</section>
ReactDOM.render(div2,document.querySelector("#app"))
// 3. jsx元素的事件就是dom元素事件添加一个on前缀,并绑定一个函数
let count = 1
function addCount(){
count++
renderDiv3()
}
function renderDiv3(){
const div3 = <div>
<div>{count}</div>
<button onClick={addCount}>点击count+1</button>
</div>
ReactDOM.render(div3,document.querySelector("#app"))
}
renderDiv3()
三、组件
react项目是由组件构成的,组件是react的基本单位。
- 组件内部使用jsx语法描述ui界面,一个组件必须返回一个jsx
- 组件的名称遵循大驼峰命名规范
1. 类组件
使用class定义组件(比较早的用法,实际开发中较少用)
console.log(React);
// class类必须继承React的Component组件类,才能使用组件的方法和属性
class Demo extends React.Component{
// Component类的属性,state是组件内部的状态,state的值改变,会触发render函数的重新执行(组件的重绘)
// 一般state状态用于更新组件
state={
age:1
}
addAge = ()=>{ // 需要用箭头函数,this才是执行当前对象
// setState也是Component类的方法,用于修改state值,只有用setState修改state,才会触发render函数的重绘
this.setState({
age: ++this.state.age // 先++,不然state值还是1,不会触发事件
})
}
name = "Anna"
// render函数是Component类的方法,该方法可以渲染一个返回的jsx
render() {
return <div>
<p>Hello{this.name}</p>
<p>年龄:{this.state.age}</p>
<button onClick={this.addAge}>age增加</button>
</div>
}
}
ReactDOM.render(<Demo/>,document.querySelector("#app"))
2. 函数组件
使用函数定义一个组件,一个函数就是一个组件,函数返回值是一个jsx
function User(){
let name = "Mario"
let age = 22
return <div>
<p>Hello{name},</p>
<p>年龄:{age}</p>
</div>
}
// 1. render 函数直接渲染组件
ReactDOM.render(<User />,document.querySelector("#app"))
// 2. 可以在jsx里面把组件做为一个元素直接调用
let div1 = <div><User /></div>
ReactDOM.render(div1,document.querySelector("#app"))
3. 组件核心属性 state
state 是组件实例对象最重要的属性,值为对象。又称为状态机,通过更新组件的 state 来更新对应的页面显示。
class Weath extends React.Component {
state = {
isHot:true
}
// 事件采用箭头函数 + 赋值语句形式
changeWeath = ()=>{
this.setState({ // 必须使用setState修改state的属性
isHot:!this.state.isHot
})
}
render(){
return <h2 onClick={this.changeWeath}>今天天气很{this.state.isHot?"炎热":"凉爽"}</h2>
}
}
ReactDOM.render(<Weath/>,document.getElementById("root"))
state的补充
class User extends React.Component{
state = {
count:1
}
addCount = ()=>{
// count state 修改完成后触发第二个回调函数参数
this.setState({count:this.state.count+1},()=>{
console.log("count 修改完成");
})
}
render(){
return <div>
<div>count:{this.state.count}</div>
<button onClick={this.addCount}>点击</button>
</div>
}
}
4. 组件核心属性 props (父传子)
props是组件的自定义属性,传递了属性都会放入props对象里面。
props属性可以让是任意数据类型,是只读属性,组件内部不能修改自身的props。
class User extends React.Component{
render(){
console.log(this.props);
return <div>
<h3>用户信息:</h3>
<p>年龄:{this.props.data.name }</p>
<p>age:{this.props.data.age}</p>
</div>
}
}
class App extends React.Component{
userInfo = {name:"Anna",age:28}
render(){
return <div>
<h3>App 组件</h3>
{/* {}是表示用js表达式,其值是一个对象(动态属性)。 */}
<User data = {this.userInfo} />
<User data = {{name:"zs",age:19}} />
<User />
</div>
}
}
ReactDOM.render(<App/>,document.querySelector("#root"))
props属性的 PropTypes /defaultProps
react15.5以后需要单独引入prop-types库
User.propTypes
和 User.defaultProps
可以看作在类
身上添加属性
,利用 static 关键词就能在类内部进行声明。因此所谓简写只是从类外部移到类内部。
<!-- 引入props属性类型校验库,可使用PropTypes对象设置数据类型 -->
<script src="https://cdn.bootcss.com/prop-types/15.6.1/prop-types.js"></script>
<script type="text/babel">
class User extends React.Component{
render(){
// console.log(this.props);
return <div>
<h3>用户信息:</h3>
</div>
}
}
// 如果props属性类型不正确,会用warnning方式提醒
console.log(PropTypes);
User.propTypes = {
data:PropTypes.object,// 对象
name:PropTypes.string,// 字符串
age:PropTypes.number,// 数值
list:PropTypes.array,// 数组
dispatch:PropTypes.func,//函数
opation:PropTypes.oneOfType([PropTypes.string,PropTypes.number]),// 符合多个类型中的一种
pop:function(props,propName,comName){ // 自定义校验器
// 第一个参数props对象,第二个参数是pop的名称,第三个是组件名称
console.log(props,propName,comName);
}
}
// 设置props属性的默认值
User.defaultProps = {
data:{name:"XXX",age:"未知"}
}
class App extends React.Component{
render(){
return <div>
<h3>App 组件</h3>
{/* {}是表示用js表达式,其值是一个对象(动态属性)。 */}
<User pop={22} opation={[]} />
</div>
}
}
ReactDOM.render(<App/>,document.querySelector("#root"))
</script>
函数组件:
下载:cnpm i prop-types
import PropTypes from "prop-types" //引入
import { connect } from "react-redux";
// 从props组件中解构
function Layouts({profile,dispatch}) {
return (
<div>
首页
</div>
)
}
// 自定义props属性的 默认值
Layouts.defaultProps = {
profile: "",
dispatch:()=>"",
}
// 自定义props属性的 数据类型
Layouts.prototype = {
profile:PropTypes.oneOfType([PropTypes.string,PropTypes.object]),
dispatch:PropTypes.func,
}
export default connect(
state=>({profile:state.user.profile}),
dispatch=>({dispatch})
)(Layouts)
state和props的区别:
- state 是组件状态,可以修改,state修改,触发render函数执行,重绘组件
- props 是组件属性,不可修改,props只能通过父组件传值。不会影响render函数的指向。
插槽
class User extends React.Component{
render(){
return <div>
{this.props.children}
</div>
}
}
class App extends React.Component{
render(){
return <div>
<h2>App:</h2>
{/* 把函数传递到子组件,子组件调用该函数并传递参数 */}
<User>
{/* 组件包裹的元素或放入props属性的children属性上 */}
<div>这是插槽内容</div>
</User>
</div>
}
}
ReactDOM.render(<App/>,document.querySelector("#root"))
5. 回调函数 (子传父 )
class User extends React.Component{
file = {name:"lolo",age:19}
render(){
return <div>
<button onClick={()=>this.props.getFile(this.file)}>点击传递</button>
</div>
}
}
class App extends React.Component{
getFile = (data)=>{
console.log(data);
}
render(){
return <div>
<h2>App:</h2>
<User getFile={this.getFile} />
</div>
}
}
ReactDOM.render(<App/>,document.querySelector("#root"))
6. 组件的条件渲染
class User extends React.Component{
age = 19
setAge = ()=>{
if (this.age>=18){
return <div>成年</div>
} else {
return <div>未成年</div>
}
}
render(){
return <div>
{/* 根据条件是否渲染组件,可以利用逻辑运算符的顺序,如果&&的左边为true会判断右边,如果左边为false不会执行右边 */}
{ this.age>=18 && <div>成年</div>}
{/* 三元表达式进行条件判断渲染,如果为null表示不渲染任何组件 */}
{this.age>=18?<div>成年</div>:<div>未成年</div>}
{/* 如果条件比较复杂,可以单独写函数进行判断 */}
{this.setAge()}
</div>
}
}
7. 组件的生命周期函数
常用(重要)的三个钩子:
componentDidMount
:组件第一次渲染完成,这里可以开启定时器、发送网络请求、订阅消息等。
render
:组件更新,一直会执行的钩子
componentWillUnmount
:组件将要被销毁,这里可以关闭定时器、取消订阅等。
旧版
即将废弃的三个钩子:componentWillMount
、componentWillReceiveProps
、componentWillUpdate
1. 挂载(旧)
// 1 构造函数,组件挂载之前调用
constructor(props){
super(props) // 在构造函数内部要使用props,需要super继承props
console.log("constructor");
}
// 3. componentDidMount 组件第一次挂载完成(第一次执行完render函数触发)
componentDidMount(){
console.log("componentDidMount");
}
// 2.初始化展示、状态更新之后
render(){
console.log("render");
}
2. 更新(旧)
// 3. shouldComponentUpdate 控制组件更新的阀门,render 函数执行前执行
// 注意:这个钩子如果不写,底层也会补一个。
shouldComponentUpdate(){ // 主要用于优化性能的生命周期
console.log("shouldComponentUpdate");
return true // 写了此钩子必须要写返回值(true会执行render更新值,false则不会执行render)
}
// 4. componentWillUpdate 组件将要更新的钩子(走render更新)
componentWillUpdate(){
console.log("componentWillUpdate");
}
// 5. componentDidUpdate 组件更新完成的钩子
componentDidUpdate(){
console.log("componentDidUpdate");
}
补充:父子组件,在子组件的一个钩子
// componentWillReceiveProps 组件将要接收新的props的钩子
//注意:第一次传的不算
componentWillReceiveProps(props){ // 参数props是接收到的数据
console.log("componentWillReceiveProps",props);
}
强制更新
// 强制更新的回调(尽量避免使用)
force=()=>{
this.forceUpdate()
}
// 1. 初始化展示、状态更新之后
render(){
return <div>
<button onClick={this.force}>不该状态,强制更新</button>
</div>
}
3. 卸载(旧)
组件卸载或者销毁前调用
// 组件即将卸载
componentWillUnmount(){
console.log("componentWillUnmount")
}
// 卸载组件的回调
del=()=>{
// 7.unmountComponentAtNode 卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById("root"))
}
render(){
return <div>
<button onClick={this.del}>删除组件</button>
</div>
}
新版
废弃3个钩子,增加两个钩子(并不常用)
1. getDerivedStateFromProps
需使用 static 修饰
需返回一个对象更新 state 或返回 null
适用于如下情况:state 的值任何时候都取决于 props
// 1.2 在render函数执行前调用,只要有render函数执行,都会调用该函数。
// 该函数有两个参数,第一个是props参数,第二个是state
// 如果有返回值,会把返回值作为state状态。没有返值或者返回false,则是使用原始state
static getDerivedStateFromProps(props,state){
console.log("getDerivedStateFromProps");
console.log(nextProps,state);
}
2. getSnapshotBeforeUpdate
在组件更新之前获取快照
得组件能在发生更改之前从 DOM 中捕获一些信息(如滚动位置)
返回值将作为参数传递给 componentDidUpdate()
// 更新前调用
getSnapshotBeforeUpdate(){
console.log("getSnapshotBeforeUpdate");
return "shot"
}
// componentDidUpdate 组件更新完成的钩子
componentDidUpdate(preProps,preState,snapshotValue){
console.log("componentDidUpdate",preProps,preState,snapshotValue);
}
8. 组件的核心属性 refs
refs可以收集带有ref属性的元素原生对象
class App extends React.Component{
componentDidMount(){ // 第一次执行完render函数触发
let h2 = this.refs.h2 // 获取原生dom对象
h2.innerText = "hello" // 还可以修改里面的值
console.log(h2);
let user = this.refs.user // 获取自定义组件对象
console.log(user);
}
render(){
return <div>
<h2 ref="h2">App</h2>
<User ref="user" />
</div>
}
}
9. form 表单数据
利用 onChange
事件
class Form extends React.Component{
state = {
name:"",
text:"",
gender:"female", // 默认值
like:["run","movie"], //复选框的值放入数组
}
// 获取多选框的值需要自己收集到数组
handleCheckbox = e=>{
console.log(e.target.value,e.target.checked);
const { value,checked } = e.target
const { like } = this.state
if (checked){ // 如果是选择,则添加到数组
like.push(value)
} else { // 如果是取消,则从数组删除该元素
like.forEach((item,index)=>{
if(item === value){
like.splice(index,1)
return
}
})
}
this.setState({like:[...like]})
}
render(){
const { name,text,gender,like } = this.state
return <form action="">
{/* 简单的双向绑定 */}
<p>姓名:<input type="text" value={name} onChange={(e)=>this.setState({name:e.target.value})} /></p>
<p>姓名:{name}</p>
<p>简介:<textarea value={text} onChange={e=>this.setState({text:e.target.value})} name=""></textarea></p>
<p>简介内容:{text}</p>
{/* 单选框 */}
<p> 性别:
<input type="radio" checked={gender==="male"} name="gender" value="male" onChange={e=>{this.setState({gender:e.target.value})}} /><label>男</label>
<input type="radio" checked={gender==="female"} name="gender" value="female" onChange={e=>{this.setState({gender:e.target.value})}} /><label>女</label>
</p>
<p>性别:{gender}</p>
{/* 复选框 */}
<p> 爱好:
<input type="checkbox" value="movie" checked={like.includes("movie")} name="like" onChange={this.handleCheckbox} /><label>电影</label>
<input type="checkbox" value="run" checked={like.includes("run")} name="like" onChange={this.handleCheckbox} /><label>跑步</label>
<input type="checkbox" value="dance" checked={like.includes("dance")} name="like" onChange={this.handleCheckbox} /><label>跳舞</label>
</p>
<p>爱好:{like}</p>
</form>
}
}
高阶函数写法:
class Form extends React.Component{
state = {
name:"",
password:"",
}
// 存表单类型
handleFormData= (dataType)=>{
return (e)=>{
console.log(dataType,e.target.value);
this.setState({ // []中可以直接传递一个变量名,这样就可以读出里面的变量值
[dataType]:e.target.value
})
}
}
render(){
const { name,password } = this.state
return <form action="">
<p>姓名:<input type="text" onChange={this.handleFormData("name")} /></p>
<p>姓名:{name}</p>
<p>密码:<input type="password" onChange={this.handleFormData("password")} name="" /></p>
<p>密码:{password}</p>
</form>
}
}
ReactDOM.render(<Form/>,document.querySelector("#root"))
10. 虚拟 DOM 与 Diff 算法
key 的作用:
key
是虚拟 DOM 对象的标识
,可提高页面更新渲染的效率。
当状态中的数据发生变化
时,React 会根据新数据
生成新的虚拟 DOM
,接着对新旧虚拟 DOM 进行 Diff 比较
,规则如下:
- 旧虚拟 DOM
找到
和新虚拟 DOM 相同的 key:
.1. 若内容没变
,直接复用
真实 DOM
.2. 若内容改变
,则生成新的
真实 DOM ,替换
页面中之前
的真实 DOM - 旧虚拟 DOM
未找到
和新虚拟 DOM 相同的 key:
根据数据创建新的真实 DOM ,直接渲染
到页面
使用 index 作为 key 可能引发的问题:
若对数据进行逆序添加
、逆序删除
等破坏顺序
的操作,会进行没有必要的真实 DOM 更新。界面效果没问题,但效率低下。
如果结构中包含输入类的 DOM(如 input 输入框) ,则会产生错误的 DOM 更新。
若不存在对数据逆序添加、逆序删除等破坏顺序的操作,则没有问题。
四、脚手架
1. 安装使用
使用传统的方式:
全局安装: npm install create-react-app -g
切换到创建文件的目录下:create-react-app 文件名
使用vite(比较快):
在安装vite的前提下
npm create vite 文件名
2. Context组件 (给任意后代传值)
Context
组件 提供了一个无需为每层组件添加props,就能在组件树之间进行数据传递(可以给任意后代
组件传递属性)
注意
: 如果有多个祖先组件都传递值,只会接收最近
那个组件传递的值
由于
props
属性只能是父子
传递,如果父组件需要传递属性到后代组件,只能一层层传递,非常麻烦
方式一:利用contextType挂载
挂载:组件名.contextType = ColorContext
取值:this.context
import { Component,createContext } from "react";
// 1. 使用createContext方法定义一个Context组件
const DateContext = createContext()
// Demo 子组件
class Demo extends Component {
render(){
return <div>
获取App的值:{this.context}
</div>
}
}
// 3.把Context对象利用contextType挂载到组件上面,用this.context获取Provider组件的value值
Demo.contextType = DateContext // 必须放在组件定义后
// App父组件
export default class App extends Component {
state = {
name:"Mario"
}
render(){
// 2. Context组件中有一个Provider的组件,该组件有一个value属性,该组件可以
// 把value值传递到包裹的任意后端组件中
return (
// Demo组件(或Demo的后代组件)都在Provider组件内部,可以使用value属性的值
<DateContext.Provider value={this.state.name}>
<div>
<h1>App!!!</h1>
<Demo />
</div>
</DateContext.Provider>
)
}
}
方式二:利用 Context.Consumer 组件
import { Component,createContext } from "react";
// 1. 使用createContext方法定义一个Context组件
const DateContext = createContext()
class Demo extends Component {
render(){
//3. Context.Consumer 组件内部是一个函数,是一个消费者,
// 函数的默认参数是Provider 的value值
return (
<div>
<DateContext.Consumer>
{
(e)=>{
console.log(e); // 这个e就是传递的值
return <h3>获取App的值:{e}</h3>
}
}
</DateContext.Consumer>
</div>
)
}
}
export default class App extends Component {
state = {
name:"Mario"
}
render(){
// 2. Context组件中有一个Provider的组件,是一个提供者,该组件有一个value属性,该组件可以
// 把value值传递到包裹的任意后端组件中
return (
// Demo组件(或Demo的后代组件)都在Provider组件内部,可以使用value属性的值
<DateContext.Provider value={this.state.name}>
<div>
<h1>App!!!</h1>
<Demo />
</div>
</DateContext.Provider>
)
}
}
切换主体样式案例(类组件)
利用style样式控制颜色
App.jsx
import { Component,createContext } from "react";
import "./App.css"
const ColorContext = createContext()
class Text2 extends Component {
render(){
return (
// 函数方式
<div>
<ColorContext.Consumer>
{e=><h2 className={e+"-con"}>这是Text2的内容...</h2>}
</ColorContext.Consumer>
</div>
)
}
}
class Text1 extends Component {
render(){
return (
<div>
<h2 className= {this.context+"-con"}>这是Text1的内容...</h2>
<Text2 />
</div>
)
}
}
Text1.contextType = ColorContext // 挂载方式
export default class App extends Component {
state = {
theme:"pink" // 默认色
}
changeColor = (e)=>{
this.setState({
theme:e.target.value
})
}
render(){
const { theme } = this.state
return (
<div className={theme}>
<header className="head">
主题色:
<select value={theme} onChange={this.changeColor}>
<option value="pink">粉色</option>
<option value="green">绿色</option>
<option value="gray">灰色</option>
</select>
</header>
<ColorContext.Provider value={theme}>
<Text1 />
</ColorContext.Provider>
</div>
)
}
}
App.css
.pink .head {
background-color: pink;
padding: 20px;
}
.green .head {
background-color: #096;
padding: 20px;
}
.gray .head {
background-color: #ccc;
padding: 20px;
}
.pink-con {
border: 1px solid pink;
color: pink;
}
.green-con {
border: 1px solid #096;
color: #096;
}
.gray-con {
border: 1px solid #ccc;
color: #ccc;
}
五、函数组件
class类组件在react16之前的版本使用较多,16版本后官方推荐
使用函数组件
函数组件内部没有类组件的属性和方法,也没有生命周期钩子,就是一个单纯函数,依赖较少
,运行效率比较高
注意
:函数组件中没有this
hook 函数:
函数组件需要使用react的内置功能,如果state等,需要引入对应的hook函数(钩子函数)
react官方把react常用的功能做成了钩子函数(hook),需要什么功能就引入对应的hook
1. props 父=>子
函数组件默认参数
就是props
属性对象,父组件传递的props属性都放在props对象里面
function Demo(props){
return <h2>
<p>{props.name}--{props.age}</p>
</h2>
}
export default function App(){
return <div>
<Demo name="RRR" age="19" />
</div>
}
// 直接解构
function Aritical({num,other}){
return (
<>
<h4>{num}</h4>
<h4>{other.content}</h4>
</>
)
}
function App() {
let appDate = {
num:100,
other:{
id:"001",
content:"SEND All DATA"
}
}
return (
<div><Aritical {...appDate} /></div>
)
}
export default App
props 子=>父
import { useState } from "react"
function Aritical({ handleShow} ){
const [show,setShow] = useState(false)
function handleChange(){
setShow(!show)
handleShow(show)
}
return (
<>
<button onClick={handleChange}>按钮</button>
<h4 style={{display:show?'block':'none'}}>显示</h4>
</>
)
}
function App() {
function receiveShow(show){
console.log(show);
}
return (
<>
<Aritical handleShow={ receiveShow } />
</>
)
}
export default App
2. useState 只能定义一个state
可以在函数组件内部使用state。userState返回一个数组
。
第一个参数是state状态
,第二个参数是设置state状态的函数
。调用第二个参数函数,可以修改state,state修改,函数会重新执行。但是state状态会被保留。
import { useState } from "react"
function Demo(){
// 第一个参数是状态名称,第二个参数函数必需是参数名称前面加set前缀。
// 可以声明多个state
const [count,setCount] = useState(0) // 设置count的默认值为0
const [age,setAge] = useState(20)
return <h2>
<p>值:{count}</p>
<button onClick={()=>{setCount(count+1)}}>count+1</button>
<p>年龄:{age}</p>
<button onClick={()=>setAge(age+1)}>年龄+1</button>
</h2>
}
export default function App(){
return <div>
<Demo />
</div>
}
定义对象、数组形式
import { Fragment, useState } from "react"
function App() {
// 1.对象结构
const [data,setData] = useState({
name:"zs",
age:20
})
function handleData(){
// 注意:修改某个属性也要把所有属性写全,防止新对象替换掉之前的旧对象
setData({
...data, // 使用展开运算符展开所有的属性
age:22
})
}
// 2.数组结构
const [list,setList] = useState([
{id:1,name:"a1"},
{id:2,name:"a2"},
{id:3,name:"a3"},
])
const listDate = list.map(item => (
// Fragment 标签相当于vue中的templete标签,不会渲染成DOM元素
<Fragment key={item.id}>
<li>{item.name}</li>
<li>-----------</li>
</Fragment>
))
function handleList(){
// setList([
// ...list,
// {id:4,name:"a4"}
// ])
setList (
list.filter(item=>item.id !== 2)
)
}
return (
<>
<button onClick={handleData}>点击修改Data</button>
<div>{data.name}--{data.age}</div>
<ul>
{listDate}
</ul>
<button onClick={handleList}>按钮</button>
</>
)
}
export default App
3. useReducer 定义多个state
useReducer(function,initState)
可以定义多个state
,是useState的替代方案(useState只能定义一个状态)
- 第一个参数
function(state,action)
函数又有两个参数,第一个参数是state状态
,action是传递参数
- 第二个参数
initState
是初始状态
import { useReducer } from "react"
// 第一个参数 函数提出来
function reducer(state,action){
if (action){
// 合并state对象和action对象,action中的值会覆盖掉state相同的值,返回新的state
return { ...state, ...action };
}
return state // 函数必需要有一个返回值,返回值作为state的值
}
// 第二个参数 初始值提出来
const initState = {
name:"ZV",age:28
}
function Demo(){
// useReducer返回一个state和dispatch函数,dispatch函数就是第一个参数function函数,传值则是action参数
const [state,dispatch] = useReducer(reducer,initState)
const changeState = ()=>{
dispatch({name:"Anna",age:19})
}
return <h2>
<p>name:{state.name}</p>
<p>age:{state.age}</p>
<button onClick={changeState}>修改值</button>
</h2>
}
export default function App(){
return <div>
<Demo />
</div>
}
4. useEffect 可监听数据变化/模拟生命周期钩子
Effect Hook 让我们能在函数式组件中执行副作用操作(就是模拟生命周期钩子
)
副作用操作:发送 Ajax 请求、定时器、手动更改真实 DOM
Effect Hook 可以模拟三个钩子:componentDidMount
、componentDidUpdate
、componentWillUnmount
React.useEffect 第一个参数 return
的函数相当于 componentWillUnmount
,若有多个会按顺序执行
useEffect(function,[params])
:第一个参数function是函数,第二个参数是监听数据的数组
import { useEffect } from "react";
// 1. 模拟componentDidMount生命周期(组件第一次渲染完成执行函数)
/* a.第二个参数如果是一个常量,则内部的函数只会执行一次。
b.第二个参数数组为空,表示不监听任何状态的更新,因此只有页面首次渲染会执行输出*/
useEffect(()=>{
console.log("第一次渲染");
},[1])
// 2. 模拟全部状态 componentDidUpdate
// 若第二个参数不写,表示监听所有状态的更新
// 数组里面监听的值发生改变,函数会执行。
useEffect(()=>{
console.log('All DidUpdate')
return () => {
console.log('WillUnmount 2')
}
})
// 3. 模拟部分状态 componentDidUpdate
// 第二个参数数组写上状态,表示只监听这些状态的更新
React.useEffect(() => {
console.log('Part DidUpdate')
return () => {
console.log('WillUnmount 3')
}
}, [count, name])
5. useContext 给任意后代组件传值
常用于祖父组件与子孙组件。实际开发一般不用,一般用 React-Redux
import { useState,createContext,useContext } from "react";
// 1.创建Context容器对象:
const XxxContext = createContext()
// 2. 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
<XxxContext.Provider value={数据}>
子组件
</XxxContext.Provider>
// 3. 后代组件使用useContext读取数据
// 获取Provider value属性的传值,需要把创建的XxxContext组件传入hook
const value = useContext(XxxContext)
例:修改count值
import { useState,createContext,useContext } from "react";
// 1.创建Context容器对象:
const CountContext = createContext()
function Demo(){
// 获取Provider value属性的传值,需要把对应的Context组件传入hook
const countValue = useContext(CountContext)
return (
<h3>后代组件展示值:{countValue}</h3>
)
}
export default function App (){
const [count,setCount] = useState(1)
return (
<CountContext.Provider value={count}>
<div>
<Demo/>
<button onClick={()=>setCount(count+1)}>祖组件修改值</button>
</div>
</CountContext.Provider>
)
}
5. useRef 获取原生dom对象(html标签)
注意:组件的ref属性不能自动绑定到ref对象
import { useEffect,useRef } from "react";
export default function App (){
const pNode = useRef(null)
useEffect(()=>{
console.log(pNode.current); //pNode.current获取原生DOM
pNode.current.innerHTML = "Change p" // 可以修改里面的内容
},[])
return (
<div>
<p ref={pNode}>这是p标签</p>
</div>
)
}
6. useCallback 缓存一个函数
useCallback(function,[params])
,缓存一个函数
,监听的params
参数发生改变,才会更新缓存。
和useEffect有点类似,主要用于缓存函数
,用于提升性能
。
实际开发新手建议少用
,对react非常熟练后再看情况使用
import { useState,useCallback } from "react";
export default function App (){
const [count,setCount] = useState(1)
const addCount = useCallback(()=>{
setCount(count+1) //会一直缓存当前值
},[count])
// 监听一个常量,则不会更新缓存。
// 如果监听的值发生改变,则会更新缓存函数
return (
<div>
<h3>Count:{count}</h3>
<button onClick={addCount}>增加</button>
</div>
)
}
7. useMemo 缓存一个值
useMemo(function,[params])
缓存一个值
,和useCallback类似。有点类似vue的计算属性。
监听的params数据改变,才会触发缓存值更新
import { useState,useMemo } from "react";
export default function App (){
const [count,setCount] = useState(1)
const getCount = useMemo(()=>{
// 函数的逻辑代表都会缓存起来,值不会变化
return "¥"+ count + "元"
},[count])
// 监听一个常量,则不会更新缓存。
// 如果监听的值发生改变,则会更新缓存函数
return (
<div>
<h3>Memo:{getCount}</h3>
<h3>Count:{count}</h3>
<button onClick={()=>setCount(count+1)}>增加</button>
</div>
)
}
8. 高阶组件(HOC) 返回新组件
一个函数
,参数为一个组件
,返回
值是一个新组件
(返回的组件就叫高阶组件)
返回高阶组件的函数就叫高阶函数。
高阶组件主要解决组件逻辑复用的一种方式,HOC是一种设计模式,并不是react的api
// 定义一个函数组件
function Money(props){
return <h3>¥{props.money}元</h3>
}
function createCom(Com){
return function NewCom(props){
return (
<div>
<p>...</p>
{/* 函数传递什么组件,Com就是什么组件 */}
<Com {...props} />
</div>
)
}
}
// NewCom1是createCom返回的组件,是一个高阶组件
const NewCom1 = createCom(Money)
export default function App (){
return (
<div>
<NewCom1 money={50}/>
</div>
)
}
9.自定义hook
自定义一个hook函数,函数名称必需以use
开头,自定义hook里面可以使用其他hook函数
例:判断分数状态
function useScore(initVal){
const [status, setStatus] = useState(initVal);
const setScore = function(val){
if (val > 90) {
setStatus("优秀");
} else if (val > 75) {
setStatus("优良");
} else if (val > 60) {
setStatus("及格");
} else {
setStatus("不及格");
}
}
// 把分数对应的状态,和判断分数状态的函数返回
return [status,setScore]
}
export default function App (){
const [val,setVal] = useState(0)
const [status,setScore] = useScore("及格");
return (
<div>
<p>
<input type="text" onChange={(e)=>setVal(e.target.value)} />
</p>
<p>状态:{status}</p>
<button onClick={()=>setScore(val)}>获取状态</button>
</div>
)
}
10. jsx插槽
// 会接收一个参数 children 为组件中包裹的内容
function Aritical({ children,title,footer=<div>默认值</div> }){
return (
<>
<h3>{title}</h3>
<ul>{children}</ul>
{footer}
</>
)
}
function App() {
return (
<>
<Aritical title="标题1" footer={<div>底部1</div>} >
<li>内容1</li>
<li>内容2</li>
<li>内容3</li>
</Aritical>
</>
)
}
export default App
六、脚手架配置代理 Proxy
单个代理
在 package.json
文件中进行配置:
"proxy": "http://localhost:5000"
优点:配置简单,前端请求资源可不加前缀
缺点:不能配置多个代理
工作方式:当请求了 3000 端口号(本机)不存在的资源时,就会把请求转发给 5000 端口号服务器
多个代理
在 src 目录下创建代理配置文件 setupProxy.js
,进行配置:
const proxy = require('http-proxy-middleware')
module.exports = function(app){
app.use(
//api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000),方便区分和限制
proxy('/api1',{
target:'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
changeOrigin:true, //控制服务器接收到的请求头中host字段的值
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为原host:localhost:3000
changeOrigin默认值为false,但一般将changeOrigin改为true,防止后台做一些限制不能正常拿数据
*/
pathRewrite:{"^/api1":""},//去除请求前缀,保证交给后台服务器的是后台中正常请求地址(必须配置)
}),
proxy('/api2',{
target:'http://localhost:5001',
changeOrigin:true,
pathRewrite:{"^/api2":""},
}),
)
}
fetch 发送请求
除了xhr封装的一些请求方法,比如axios…(需引入/下载)
fetch不用下载和引入,浏览器自带的网络请求方法,比原生的xhr好用一点。关注分离的设计思想
search = async ()=>{
try {
// 先看服务器是否联系得上
//只会返回正确的Promise对象
const response = await fetch('https://...')
// 通过json方法 再获取里面数据
const data = await response.json()
console.log(data)
} catch (error) { // 出错统一返回错误信息
console.log('请求出错', error)
}
}
七、消息订阅-发布机制(兄弟间通信)
即 React 中兄弟组件或任意组件之间的通信方式。
先订阅,再发布(隔空对话)
要在 componentWillUnmount
钩子中取消订阅
下载安装 PubSubJS :npm install pubsub-js --save
import PubSub from 'pubsub-js' // 引入
// 1.订阅消息 subscribe
var token = PubSub.subscribe('topic', (msg, data) => {
console.log(data)
})
// 2.发布消息 publish
PubSub.publish('topic', 'hello reactXXXX')
// 3.取消订阅
componentWillUnmount(){
PubSub.unsubscribe(token)
}
八、路由
安装:npm i react-router-dom
1. BrowserRouter
BrowserRouter
路径都是 …/home
Routes
是创建路由管理器,
Route
是创建路由对象
import { BrowserRouter,Route,Routes,Link } from "react-router-dom"
import Home from "./pages/home"
import List from "./pages/list"
export default function App (){
return (
<BrowserRouter>
<div>
{/* Link 跳转路由组件,to是路由path的路径 */}
<Link to="/home">home</Link> <br />
<Link to="/list">list</Link>
</div>
{/* Routes 渲染路由的组件,只会渲染一个符合条件的Route */}
<Routes>
{/* Route 配置路由的组件,path是路由路径,element是路由渲染的组件 */}
<Route path="/home" element={<Home />} />
<Route path="/list" element={<List />} />
</Routes>
</BrowserRouter>
)
}
2. HashRouter
HashRouter
路径都是 …#/home
export default function App (){
return (
<HashRouter>
<div>
{/* Link 跳转路由组件,to是路由path的路径 */}
<Link to="/home">home</Link> <br />
<Link to="/list">list</Link>
</div>
{/* Routes 渲染路由的组件,只会渲染一个符合条件的Route */}
<Routes>
{/* Route 配置路由的组件,path是路由路径,element是路由渲染的组件 */}
<Route path="/home" element={<Home />} />
<Route path="/list" element={<List />} />
</Routes>
</HashRouter>
)
}
3. 获取动态参数 useParams
export default function App (){
return (
<HashRouter>
<div>
{/* 路由格式要正确 */}
<Link to="/home/1/122">home</Link> <br />
</div>
<Routes>
{/* 动态路由参数,不支持正则匹配,可以指定多个动态路由参数 */}
<Route path="/home/:id/:num" element={<Home />} />
</Routes>
</HashRouter>
)
}
Home.jsx
import { useParams } from 'react-router-dom'
export default function Home() {
// 获取动态路由参数,需要使用 useParams hook函数,返回一个动态路由参数对象
const params = useParams()
console.log(params);// {id: '1', num: '122'}
return (
<div>
<h2>Home</h2>
</div>
)
}
4. 嵌套路由
import { HashRouter,Route,Routes } from "react-router-dom"
import Layout from "./pages/layout"
import Home from "./pages/home"
import List from "./pages/list"
export default function App (){
return (
<HashRouter>
<Routes>
{/* 嵌套路由 */}
<Route path="/layout" element={<Layout />}>
{/* 嵌套下面的路由的路径不要带 / */}
<Route path="home" element={<Home/>} />
<Route path="list" element={<List/>} />
</Route>
{/* *匹配全部路由,一般放在最后,匹配所有路由 */}
<Route path="*" element={<NotFound/>} />
</Routes>
</HashRouter>
)
}
Outlet
import { Link,Outlet } from "react-router-dom"
export default function Layout() {
return (
<div>
<h2>Layout...</h2>
<ul>
{/* 这里路径要写完整 */}
<li><Link to="/layout/home">首页</Link></li>
<li><Link to="/layout/list">列表</Link></li>
</ul>
<main>
{/* Outlet表示嵌套路由组件显示的位置 */}
<Outlet />
</main>
</div>
)
}
5. 跳转路由
import { useNavigate } from "react-router-dom"
export default function Home() {
// 使用 useNavigate 返回一个路由对象
const navigator = useNavigate()
const goPage = ()=>{
// 编程式导航跳转路由
navigator("/layout")
}
return (
<div>
<h2>Home...</h2>
<button onClick={goPage}>跳转Layout页面</button>
</div>
)
}
6. 获取当前路由 useLocation
import { useLocation } from "react-router-dom"
const location = useLocation()
console.log(location);
九、redux状态管理
和vuex类似
安装:cnpm i @reduxjs/toolkit
1. 原始 redux
import { configureStore,combineReducers } from "@reduxjs/toolkit";
// 1. 创建reducer函数,返回一个state
// reducer 函数主要是处理state(初始化state,修改state)
// 需要给state赋值一个初始值
// action是actions触发reducer函数传递的参数
// 注意:state一定要定义初始值
function reducer(state={a=0},action){
// 一般reducer函数内部根据action的参数type做逻辑分支处理
...
console.log(state,action);
return state
}
// 2. 使用configureStore方法创建store,需要传入reducer方法
// 注意:这里传入的是一个对象
const store = configureStore({reducer:reducer})
// 3. 监听store里面reducer函数返回的state变化,只要有变化就会触发subscript函数
store.subscribe(function(){
// 获取全局状态的state
const state = store.getState()
})
// 4. action触发reducer函数。dispatch就可触发reducer函数并传递参数
store.dispatch({type:"xxx",count:0})
export default store // 暴露store
combineReducers
可以合并
多个reducer,主要用于reducer模块化
import { configureStore,combineReducers } from "@reduxjs/toolkit";
// combineReducers可以合并多个reducer,主要用于reducer模块化
// 创建reducer1:
// 初始值
const initUserDate = {profile:""}
const reducer1 = function(state=initUserDate,action){
const { type,data } = action
// 分模块,type一般需要带上模块的名称以和其他模块区分
...
}
// 创建reducer2
const globalInit = {}
const reducer2 = function(state=globalInit,action){
const { type,date } = action
...
}
const reducer = combineReducers({
user:reducer1,
global:reducer2
})
const store = configureStore({reducer})
export default store
改变count
import { configureStore } from "@reduxjs/toolkit";
// 注意:state一定要定义初始值
function reducer(state={count:0},action){
switch (action.type) {
case "add":
return {...state,count:state.count + action.count}
default:
break;
}
return state
}
// 注意:这里传入的是一个对象
const store = configureStore({reducer})
export default store // 暴露store
import store from "../store"
import { useEffect,useState } from "react"
export default function Home() {
const [count,setCount] = useState(0)
// 第一次页面渲染完成就要修改值
useEffect(()=>{
const con = store.getState()
setCount(con.count)
},[1])
// 监听store里面的值,保证页面随之改变
store.subscribe(()=>{
const res = store.getState()
setCount(res.count)
})
// 操作count
const addCount = ()=>{
store.dispatch({type:"add",count:6})
}
return (
<div>
<p>count:{count}</p>
<button onClick={addCount}>改变count</button>
</div>
)
}
2. 第三方redux(使用较多)
安装:npm i react-redux
react-redux
将redux绑定到react里面,主要是把dispatch
方法和state
状态绑定到
react组件的props
属性
store的三大功能:dispatch,subscribe,getState都不需要手动来写了。react-redux帮我们做了这些,同时它提供了两个好用的Provider
和connect
。
// Provider组件可以将store传递到包裹的组件里面
import { Provider } from "react-redux"
import store from "./store"
export default function App (){
return (
<Provider store={store}>
...
</Provider>
)
}
connect 接受当前组件作为参数,返回一个新组件,该组件会把全局store的state和dispatch放到组件的props属性上
两个参数:
mapStateToProps
是一个函数,参数是store里面的state
对象,返回一个对象并返回的对象放入合并到props属性
mapDispatchToProps
把store里面的dispatch
对象放入合并到props属性
// connect是一个高阶函数,有两个参数,并需要传递当前组件
import { connect } from "react-redux"
function Layout(props) {
console.log(props); // 可以拿到全局的state
return (
<div>
<h2>Layout...</h2>
</div>
)
}
const mapStateToProps = (state)=>{
return state
}
const mapDispatchToProps = (dispatch)=>{
// 把dispatch属性合并到props属性,它最终都会返回一个对象
return {dispatch,}
}
// 它内部定义一个新组件Connect(容器组件)并将传入的组件(ui组件)作为Connect的子组件然后return出去
export default connect(mapStateToProps,mapDispatchToProps)(Layout)
// 简写
export default connect((state)=>(state),dispatch=>({dispatch}))(Layout)
十、项目搭建
1. 准备
- 使用vite:
npm create vite
- 安装必要包:
npm i react-router-dom redux react-redux
- 安装UI库:
npm install antd
- 安装sass:
npm i sass-loader node-sass -D
注意防止样式冲突问题:react中自带有样式命名规定xxx.module.scss
命名时:
index.module.scss
引用sass样式:import styles from "./index.module.scss"
项目中:使用ESLint 关注代码质量,Prettier 关注代码风格
安装:cnpm install prettier eslint-config-prettier eslint-plugin-prettier -D
2. 大包 build
打包:npm run build
默认build打包是把所有组件的js打包到一个js,项目比较大,js文件大,加载就慢,用户体验不好
分片打包(组件的懒加载),每个组件的js单独打包,跳转到对应路由才加载相关组件 分片打包的使用lazy函数,可以实现js组件懒加载
lazy函数加载的组件必须包裹 Suspense 组件
import { lazy,Suspense } from "react"
import { HashRouter,Routes,Route } from "react-router-dom"
// import Login from "./pages/login"
// import Dashboard from "./pages/dashboard"
let Login = lazy(()=> import("./pages/login"))
let Dashboard = lazy(()=> import("./pages/dashboard"))
//定义一个高阶函数,传组件进来
function getLazyCom(Com){
return <Suspense fallback={<div>Loading...</div>}>
<Com />
</Suspense>
}
export default function Router() {
return (
<HashRouter>
<Routes>
<Route path="/login" element={getLazyCom(Login)} />
<Route path="/" element={getLazyCom(Layout)}>
<Route path="dashboard" element={getLazyCom(Dashboard)} />
</Route>
</Routes>
</HashRouter>
)
}
React使用的UI库 antd
Ant Design(简称antd)
网址:https://ant.design/index-cn/
富文本编辑器wangeditor:https://www.wangeditor.com
一些 Echart 图网站:
- DataV:
http://datav-react.jiaminghi.com/
- Apache ECharts:
https://echarts.apache.org/zh/index.html