佛山建企业网站,114网站制作,我要建设公司网站,重庆辣肠怎么制作React框架
1.前端展示解释
当客户端访问服务器时#xff0c;会从服务器中下载很多静态文件到本地#xff0c;比如css、js等前端渲染文件 下载完成之后浏览器会将这些文件组合形成前端页面渲染出来。 2.React概述
React是一个专注于构建用户界面的JavaScript库#xff0c;…React框架
1.前端展示解释
当客户端访问服务器时会从服务器中下载很多静态文件到本地比如css、js等前端渲染文件 下载完成之后浏览器会将这些文件组合形成前端页面渲染出来。 2.React概述
React是一个专注于构建用户界面的JavaScript库它采用声明式编程范式使得代码更加易于阅读和理解。React的核心思想是组件化即将用户界面划分为独立的、可复用的组件。每个组件都有自己的状态和生命周期方便开发者进行维护和复用。
React特性1虚拟DOM树
DOM树是集中保存一个网页中所有内容的树形结构 而React框架会在内存中维护一个虚拟的DOM树它是实际DOM的轻量级内存表示。当网页源代码中网页内容状态或者属性发生变化时React会重新计算虚拟DOM树并通过比较新旧虚拟DOM树的差异Diffing找出需要更新的部分。最后将这些变化批量应用到实际DOM上从而减少不必要的重绘和回流提高性能。
React特性2JSX语法扩展
React引入了JSXJavaScript XML语法扩展允许在JavaScript中编写类似HTML的结构。这种语法使得开发者可以更加直观地描述用户界面同时保持代码的灵活性和可维护性。 JSX编译成JS编写JSX的语法更加简单灵活。 编译通过React提供的Babel编译器将JSX代码编译成JS代码。 3.环境配置
1.按照终端Git Bash
安装地址Git Bash官网windows
2.安装Nodejs
安装地址Nodejs
3.安装create-react-app
打开Git Bash执行
npm i -g create-react-app4.安装VSCode插件
Simple React Snippets 提供一些react常用命令的自动补全Prettier - Code formatter 代码高亮
5.创建React App
当需要使用React开发一个App时
在目标目录下打开Git Bash,执行
create-react-app react-app #可以替换为其他名称cd react-app
npm start #启动应用4.React初始项目结构
1.node_modules
负责维护JS库各种JS相关的轮子
2.public
index.html:主页面的渲染 以及一些静态文件
3.src
主界面以及其他组件内容的css和js文件 5.ES6语法
1.使用bind()函数绑定this取值
在JavaScript中函数里的this指向的是执行时的调用者而不是定义时所在的对象。
例如
const animal {name: dog,talk: function() {console.log(this);}
}animal.talk();const talk animal.talk;
talk();运行结果是
animal.talk()它会根据调用的对象来给talk()里面的this赋值 而将animal.talk()赋值给当前文件的成员再执行该成员调用talk()方法时由于当前文件是由window调用的那么talk()里面的this就变成了Window。 而为了避免这种情况使用bind()函数可以绑定this的取值例如
const talk animal.talk.bind(animal);就可以将该对象绑定到重新赋值的成员上不会导致错误的取值。 2.箭头函数的简写
const f (x) {return x * x;
}
const f1 x x * x;console.log(f(3), f1(3));运行结果
9 9 3.通过箭头函数绑定this的取值
const animal {talk: function() {setTimeout(function() {console.log(this);}, 1000);}
};animal.talk();在上述代码中虽然talk()函数是由animal调用的但是里面的function函数其实还是Window执行的所以里面的函数取的this是当前Window。
为避免这种情况一般的写法是
const animal {talk: function() {let outer this;setTimeout(function() {console.log(outer);}, 1000);}
};animal.talk();让里层的outer指向外层的animal对象。
而使用箭头函数可以直接规避这种情况
const animal {talk: function() {setTimeout(() {console.log(this);}, 1000);}
};animal.talk();运行结果 4.对象的解构
例如
const animal {name: dog,age: 12,height: 100,
};const {name : new_name, age} animal; //new_name是name的别名console.log(new_name, age);打印结果
5.数组和对象的展开
let a [1, 2, 3];
let b [4, 5, 6];
let c [...a]; //c是a的复制let d [...c, ...b]; //将c和b展开放到d中console.log(d);const A {name: dog};
const B {age: 12};
const C {...A, ...B, heigth: 100}; //将对象元素展开并放入C中console.log(C);打印结果
6.Named 与 Default exports
Named Export可以export多个import的时候需要加大括号名称需要匹配Default Export 最多export一个import的时候不需要加大括号可以直接定义别名
export default class Player {constructor() {console.log(new Player);}
}import MyPlayer from ./Player //默认值不能加大括号let player new MyPlayer();console.log(player);打印结果 6.Component
示例项目实现两个按钮控制一个box左右移动
1创建box-app项目
crete-react-app box-app
cd box-app
npm start //启动box-app2安装bootstrap库
npm i bootstrap在项目中导入bootstrap库
import bootstrap/dist/css/bootstarp.css3创建Component
一般将组件全部维护在一个component文件夹下 先创建component文件夹然后创建box.jsx文件。
4创建按钮
当子节点数量大于1个时需要用div或React.Fragment将其括起来。 同时整个部分用()括起来return
import React, { Component } from reactclass Box extends Component {state { } render() { return (/* divh1hello world/h1buttonleft/buttonbuttonright/button/div */React.Fragmenth1hello world/h1buttonleft/buttonbuttonright/button/React.Fragment);}
}export default Box;5内嵌表达式
JSX中使用{}嵌入表达式
import React, { Component } from reactclass Box extends Component {state { x: 1,};render() { return (/* divh1hello world/h1buttonleft/buttonbuttonright/button/div */React.Fragmentdiv{this.state.x}/divdiv{this.toString()}/divbuttonleft/buttonbuttonright/button/React.Fragment);}toString() {return x: ${this.state.x};}
}export default Box;页面展示
6 设置属性
通过设置className来对应属性
通过bootstrap找到已经设计好的属性类进行渲染。
bootstrap官网搜索需要的示例样式
CSS属性不同于普通cssReact中要求中间使用-连接的属性需要改成驼峰命名比如background-color: backgrounColor其他属性类似。
设置属性
import React, { Component } from reactclass Box extends Component {state { x: 1,};styles {width: 50px,height: 50px,backgroundColor: lightblue}render() { return (/* divh1hello world/h1buttonleft/buttonbuttonright/button/div */React.Fragmentdiv style{this.styles}{this.toString()}/divbutton classNamebtn btn-primary m-2left/buttonbutton classNamebtn btn-success m-2right/button/React.Fragment);}toString() {const x this.state.x;return x: ${x};}
}export default Box;简写方式将属性以数组的形式传入style中
import React, { Component } from reactclass Box extends Component {state { x: 1,};render() { return (/* divh1hello world/h1buttonleft/buttonbuttonright/button/div */React.Fragmentdiv style{{width: 50px,height: 50px,color: white,textAlign: center,lineHeight: 50px,borderRadius: 5px,backgroundColor: lightblue}}{this.toString()}/divbutton classNamebtn btn-primary m-2left/buttonbutton classNamebtn btn-success m-2right/button/React.Fragment);}toString() {const x this.state.x;return x: ${x};}
}
export default Box;页面展示 7数据驱动改变Style
通过改变一个变量的值从而改变组件的style样式
import React, { Component } from reactclass Box extends Component {state { x: 0,};render() { return (/* divh1hello world/h1buttonleft/buttonbuttonright/button/div */React.Fragmentdiv style{this.getStyles()}{this.toString()}/divbutton classNamebtn btn-primary m-2left/buttonbutton classNamebtn btn-success m-2right/button/React.Fragment);}getStyles() {let styles {width: 50px,height: 50px,color: white,textAlign: center,lineHeight: 50px,borderRadius: 5px,backgroundColor: lightblue}if (this.state.x 0) {styles.backgroundColor orange;}return styles;}toString() {const x this.state.x;return x: ${x};}
}
export default Box;
页面展示 当改变x的值时div对应的样式会发生改变。
8渲染列表
使用map函数每个元素需要具有唯一的key属性用来帮助React快速找到被修改的DOM元素。
关于key的面试题
面试题react、vue中的key有什么作用key的内部原理
虚拟DOM中key的作用 key是虚拟DOM对象的标识当数据发生变化时Vue会根据【新数据】生成【新的虚拟DOM】
随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较比较规则如下 在旧虚拟DOM中找到与新虚拟DOM相同的key 若虚拟DOM元素内容没变, 直接使用之前的真实DOM元素 若虚拟DOM元素内容变了, 则生成新的真实DOM元素随后替换掉页面中之前的真实DOM元素。
列表渲染示例 使用map函数将列表中的元素内容渲染依次渲染出来。
import React, { Component } from reactclass Box extends Component {state { x: 0,colors: [red, yellow, blue],};render() { return (/* divh1hello world/h1buttonleft/buttonbuttonright/button/div */React.Fragmentdiv style{this.getStyles()}{this.toString()}/divbutton classNamebtn btn-primary m-2left/buttonbutton classNamebtn btn-success m-2right/button{this.state.colors.map(color (div key{color}{color}/div))}/React.Fragment);}getStyles() {let styles {width: 50px,height: 50px,color: white,textAlign: center,lineHeight: 50px,borderRadius: 5px,backgroundColor: lightblue}if (this.state.x 0) {styles.backgroundColor orange;}return styles;}toString() {const x this.state.x;return x: ${x};}
}
export default Box;页面展示 9绑定事件
在添加绑定事件时需要注意 这里同样会发生this的值变成其他不明的指代这就导致我们无法知道React在实现的时候是在什么东西调用的click函数但是我们希望它在调用click时指向的是当前的结构体box class).
于是跟之前补充的ES6语法一致要么通过箭头函数推荐因为箭头函数不会重新给this赋值也就是说在调用箭头函数实现的click函数时它指向的this就是原本我们赋给它的this。 另外一种方法就是利用bind函数绑定this。
import React, { Component } from reactclass Box extends Component {state { x: 0,colors: [red, yellow, blue],};handleClickLeft() {console.log(click left, this);}handleClickRight() { console.log(click right, this);}render() { return (/* divh1hello world/h1buttonleft/buttonbuttonright/button/div */React.Fragmentdiv style{this.getStyles()}{this.toString()}/divbutton onClick{this.handleClickLeft} classNamebtn btn-primary m-2left/buttonbutton onClick{this.handleClickRight} classNamebtn btn-success m-2right/button{this.state.colors.map(color (div key{color}{color}/div))}/React.Fragment);}getStyles() {let styles {width: 50px,height: 50px,color: white,textAlign: center,lineHeight: 50px,borderRadius: 5px,backgroundColor: lightblue}if (this.state.x 0) {styles.backgroundColor orange;}return styles;}toString() {const x this.state.x;return x: ${x};}
}
export default Box;这里打印this值会发现当前的this值是未定义的也就是说确实不是当前结构体box class
两种方式绑定this值不发生改变
import React, { Component } from reactclass Box extends Component {state { x: 0,colors: [red, yellow, blue],};handleClickLeft () {console.log(click left, this);}handleClickRight() { console.log(click right, this);}render() { return (/* divh1hello world/h1buttonleft/buttonbuttonright/button/div */React.Fragmentdiv style{this.getStyles()}{this.toString()}/divbutton onClick{this.handleClickLeft} classNamebtn btn-primary m-2left/buttonbutton onClick{this.handleClickRight.bind(this)} classNamebtn btn-success m-2right/button{this.state.colors.map(color (div key{color}{color}/div))}/React.Fragment);}getStyles() {let styles {width: 50px,height: 50px,color: white,textAlign: center,lineHeight: 50px,borderRadius: 5px,backgroundColor: lightblue}if (this.state.x 0) {styles.backgroundColor orange;}return styles;}toString() {const x this.state.x;return x: ${x};}
}
export default Box;打印展示 10修改state
需要使用this.setState()函数每次调用this.setState()函数后会重新调用this.render()函数用来修改虚拟机DOM树。React只会修改不同步的实际DOM树节点。
当我们直接修改state里面的某个变量值时虽然该变量确实发生改变但是react无法将该变量的改变同步渲染只有通过调用this.setState()函数来修改react才会重新调用this.render()函数来修改虚拟DOM树从而修改不同步的实际DOM树节点。
** 不调用this.setState()函数时**
import React, { Component } from reactclass Box extends Component {state { x: 0,colors: [red, yellow, blue],};handleClickLeft () {this.state.x--;console.log(click left, this.state.x);}handleClickRight () { this.state.x;console.log(click right, this.state.x);}render() { return (/* divh1hello world/h1buttonleft/buttonbuttonright/button/div */React.Fragmentdiv style{this.getStyles()}{this.toString()}/divbutton onClick{this.handleClickLeft} classNamebtn btn-primary m-2left/buttonbutton onClick{this.handleClickRight} classNamebtn btn-success m-2right/button{this.state.colors.map(color (div key{color}{color}/div))}/React.Fragment);}getStyles() {let styles {width: 50px,height: 50px,color: white,textAlign: center,lineHeight: 50px,borderRadius: 5px,backgroundColor: lightblue}if (this.state.x 0) {styles.backgroundColor orange;}return styles;}toString() {const x this.state.x;return x: ${x};}
}
export default Box;通过调用this.setState()函数来修改state
import React, { Component } from reactclass Box extends Component {state { x: 0,colors: [red, yellow, blue],};handleClickLeft () {this.setState({x: this.state.x - 1,});console.log(click left, this.state.x);}handleClickRight () { this.setState({x: this.state.x 1,});console.log(click right, this.state.x);}render() { return (/* divh1hello world/h1buttonleft/buttonbuttonright/button/div */React.Fragmentdiv style{this.getStyles()}{this.toString()}/divbutton onClick{this.handleClickLeft} classNamebtn btn-primary m-2left/buttonbutton onClick{this.handleClickRight} classNamebtn btn-success m-2right/button{this.state.colors.map(color (div key{color}{color}/div))}/React.Fragment);}getStyles() {let styles {width: 50px,height: 50px,color: white,textAlign: center,lineHeight: 50px,borderRadius: 5px,backgroundColor: lightblue}if (this.state.x 0) {styles.backgroundColor orange;}return styles;}toString() {const x this.state.x;return x: ${x};}
}
export default Box;将marginLeft设置为当前this.state.x这样每次点击左右就会改变this.state.x的值通过this.setState()就会每次改变都会重新调用this.render()函数而此时marginLeft的值与this.state.x相关就能实现点击right按钮时box块往右移动marginLeft变大点击left按钮时box块往左移动marginLeft变小。 import React, { Component } from reactclass Box extends Component {state { x: 0,colors: [red, yellow, blue],};handleClickLeft () {this.setState({x: this.state.x - 1,});console.log(click left, this.state.x);}handleClickRight () { this.setState({x: this.state.x 1,});console.log(click right, this.state.x);}render() { return (/* divh1hello world/h1buttonleft/buttonbuttonright/button/div */React.Fragmentdiv style{this.getStyles()}{this.toString()}/divbutton onClick{this.handleClickLeft} classNamebtn btn-primary m-2left/buttonbutton onClick{this.handleClickRight} classNamebtn btn-success m-2right/button{this.state.colors.map(color (div key{color}{color}/div))}/React.Fragment);}getStyles() {let styles {width: 50px,height: 50px,color: white,textAlign: center,lineHeight: 50px,borderRadius: 5px,backgroundColor: lightblue,marginLeft: this.state.x,}if (this.state.x 0) {styles.backgroundColor orange;}return styles;}toString() {const x this.state.x;return x: ${x};}
}
export default Box; 11给事件函数添加参数
import React, { Component } from reactclass Box extends Component {state { x: 0,colors: [red, yellow, blue],};handleClickLeft (step) {this.setState({x: this.state.x - step,});console.log(click left, this.state.x);}handleClickRight () { this.setState({x: this.state.x 1,});console.log(click right, this.state.x);}handleClickLeftTmp () {return this.handleClickLeft(10);}render() { return (/* divh1hello world/h1buttonleft/buttonbuttonright/button/div */React.Fragmentdiv style{this.getStyles()}{this.toString()}/divbutton onClick{this.handleClickLeftTmp} classNamebtn btn-primary m-2left/buttonbutton onClick{this.handleClickRight} classNamebtn btn-success m-2right/button{this.state.colors.map(color (div key{color}{color}/div))}/React.Fragment);}getStyles() {let styles {width: 50px,height: 50px,color: white,textAlign: center,lineHeight: 50px,borderRadius: 5px,backgroundColor: lightblue,marginLeft: this.state.x,}if (this.state.x 0) {styles.backgroundColor orange;}return styles;}toString() {const x this.state.x;return x: ${x};}
}
export default Box;页面结果展示 匿名函数写法
import React, { Component } from reactclass Box extends Component {state { x: 0,colors: [red, yellow, blue],};handleClickLeft (step) {this.setState({x: this.state.x - step,});console.log(click left, this.state.x);}handleClickRight (step) { this.setState({x: this.state.x step,});console.log(click right, this.state.x);}render() { return (/* divh1hello world/h1buttonleft/buttonbuttonright/button/div */React.Fragmentdiv style{this.getStyles()}{this.toString()}/divbutton onClick{() this.handleClickLeft(10)} classNamebtn btn-primary m-2left/buttonbutton onClick{() this.handleClickRight(10)} classNamebtn btn-success m-2right/button{this.state.colors.map(color (div key{color}{color}/div))}/React.Fragment);}getStyles() {let styles {width: 50px,height: 50px,color: white,textAlign: center,lineHeight: 50px,borderRadius: 5px,backgroundColor: lightblue,marginLeft: this.state.x,}if (this.state.x 0) {styles.backgroundColor orange;}return styles;}toString() {const x this.state.x;return x: ${x};}
}
export default Box;7.React组件化实现表格
实现表格的删除和复制功能将对应的元素删除或者复制。
主要知识点
1.使用map函数遍历数组中的元素进行表格内容的填充2.点击事件的绑定3.使用箭头函数进行参数的传递4.使用this.setState()函数更新
注意事项每个组件需要唯一的key进行标识这里使用单独设定的key其他场景可以另外指代
import React, { Component } from react;class Solution extends Component {state { solutions: [{key: 51, number: 2000, title: 高质量文章1, views: 649, subscribes: 105293},{key: 52, number: 2001, title: 高质量文章2, views: 6491, subscribes: 105093},{key: 53, number: 2002, title: 高质量文章3, views: 6492, subscribes: 10593},{key: 54, number: 2003, title: 高质量文章4, views: 6493, subscribes: 105935},{key: 55, number: 2004, title: 高质量文章5, views: 6494, subscribes: 105593},{key: 56, number: 2005, title: 高质量文章6, views: 6495, subscribes: 105963},{key: 57, number: 2006, title: 高质量文章7, views: 6496, subscribes: 105793},{key: 58, number: 2007, title: 高质量文章8, views: 6497, subscribes: 105893},{key: 59, number: 2008, title: 高质量文章9, views: 6498, subscribes: 105993},{key: 60, number: 2009, title: 高质量文章10, views: 6499, subscribes: 105493},{key: 61, number: 2010, title: 高质量文章11, views: 6490, subscribes: 1059133},{key: 62, number: 2011, title: 高质量文章12, views: 6459, subscribes: 1059453},{key: 63, number: 2012, title: 高质量文章13, views: 64945, subscribes: 10594853}]} handleDelete (s) {console.log(delete);//将跟s不一样的元素都保留下来过滤掉sconst solutions this.state.solutions.filter(solution solution ! s);this.setState({solutions: solutions, //将当前的key更新为value key:value});}handleIncrease (s) {const solutions [...this.state.solutions, {key: s.key 1, number: s.number, title: s.title, views: 0, subscribes: 0}]this.setState({solutions: solutions,})}render() { if (this.state.solutions.length 0) {return p没有文章啦~/p}return (table classNametabletheadtrth文章序号/thth标题/thth阅读/thth订阅/thth操作/th/tr/theadtbody{this.state.solutions.map(solution (tr key{solution.key}td{solution.number}/tdtd{solution.title}/tdtd{solution.views}/tdtd{solution.subscribes}/tdtdbutton onClick{() this.handleDelete(solution)} classNamebtn btn-danger删除/buttonbutton onClick{() this.handleIncrease(solution)} classNamebtn btn-success复制/button/td/tr))}/tbody/table);}
}export default Solution;页面展示 8.组合Components
1创建Boxes组件
在Boxes组件中包含一系列Box组件 将Box导入到Boxes中然后在Boxes中创建多个Box组件
import React, { Component } from react;import Box from ./box;class Boxes extends Component {state { boxes: [{id: 1, x: 0},{id: 2, x: 0},{id: 3, x: 0},{id: 4, x: 0},]} render() { return (React.Fragment{this.state.boxes.map(box (Box key{box.id}/))}/React.Fragment);}
}export default Boxes;2从上往下传递数据
通过this.props属性可以从上到下传递数据
boxes.jsx:
import React, { Component } from react;import Box from ./box;class Boxes extends Component {state { boxes: [{id: 1, x: 1},{id: 2, x: 2},{id: 3, x: 3},{id: 4, x: 4},]} render() { return (React.Fragment{this.state.boxes.map(box (Box key{box.id}x{box.x}/))}/React.Fragment);}
}export default Boxes;box.jsx:
import React, { Component } from reactclass Box extends Component {state { x: this.props.x,};handleClickLeft (step) {this.setState({x: this.state.x - step,});console.log(click left, this.state.x);}handleClickRight (step) { this.setState({x: this.state.x step,});console.log(click right, this.state.x);}render() { return (React.Fragmentdiv style{this.getStyles()}{this.toString()}/divbutton onClick{() this.handleClickLeft(10)} classNamebtn btn-primary m-2left/buttonbutton onClick{() this.handleClickRight(10)} classNamebtn btn-success m-2right/button/React.Fragment);}getStyles() {let styles {width: 50px,height: 50px,color: white,textAlign: center,lineHeight: 50px,borderRadius: 5px,backgroundColor: lightblue,marginLeft: this.state.x,}if (this.state.x 0) {styles.backgroundColor orange;}return styles;}toString() {const x this.state.x;return x: ${x};}
}
export default Box;页面展示 在主组件中传入参数使得子组件的参数发生对应变化 3传递子节点
通过this.props.children属性传递子节点
boxes.jsx:
import React, { Component } from react;import Box from ./box;class Boxes extends Component {state { boxes: [{id: 1, x: 1},{id: 2, x: 2},{id: 3, x: 3},{id: 4, x: 4},]} render() { return (React.Fragment{this.state.boxes.map(box (Box key{box.id}x{box.x}h1Box:/h1p#{box.id}/p/Box))}/React.Fragment);}
}export default Boxes;box.jsx:
import React, { Component } from reactclass Box extends Component {state { x: this.props.x,};handleClickLeft (step) {this.setState({x: this.state.x - step,});console.log(click left, this.state.x);}handleClickRight (step) { this.setState({x: this.state.x step,});console.log(click right, this.state.x);}render() { console.log(this.props);return (React.Fragment{this.props.children[0]}div style{this.getStyles()}{this.toString()}/div{this.props.children[1]}button onClick{() this.handleClickLeft(10)} classNamebtn btn-primary m-2left/buttonbutton onClick{() this.handleClickRight(10)} classNamebtn btn-success m-2right/button/React.Fragment);}getStyles() {let styles {width: 50px,height: 50px,color: white,textAlign: center,lineHeight: 50px,borderRadius: 5px,backgroundColor: lightblue,marginLeft: this.state.x,}if (this.state.x 0) {styles.backgroundColor orange;}return styles;}toString() {const x this.state.x;return x: ${x};}
}
export default Box;页面展示 4从下往上调用函数
注意每个组件的this.state只能在组件内部修改不能在其他组件内修改 相当于私有组件只能由自己调用和修改
子组件调用父组件的方法通过this.props属性将父组件的函数指代传递给子组件当子组件获取到父组件的函数指代后可以通过该指代以及传递下来的参数去调用父组件的函数。
boxes.jsx:
import React, { Component } from react;import Box from ./box;class Boxes extends Component {state { boxes: [{id: 1, x: 1},{id: 2, x: 2},{id: 3, x: 3},{id: 4, x: 4},]} handleDelet (boxId) {console.log(handle delete, boxId);const boxes this.state.boxes.filter(b b.id ! boxId);this.setState({boxes: boxes});}render() { return (React.Fragment{this.state.boxes.map(box (Box key{box.id}x{box.x}//将函数以及函数需要用到的参数传递给子组件id{box.id}onDelete{this.handleDelet} //子组件在获取到onDelete时实际是获取到了父组件的handleDelete方法的指代h1Box:/h1p#{box.id}/p/Box))}/React.Fragment);}
}export default Boxes;box.jsx: import React, { Component } from reactclass Box extends Component {state { x: this.props.x,};handleClickLeft (step) {this.setState({x: this.state.x - step,});console.log(click left, this.state.x);}handleClickRight (step) { this.setState({x: this.state.x step,});console.log(click right, this.state.x);}render() { return (React.Fragment{this.props.children[0]}div style{this.getStyles()}{this.toString()}/div{this.props.children[1]}button onClick{() this.handleClickLeft(10)} classNamebtn btn-primary m-2left/buttonbutton onClick{() this.handleClickRight(10)} classNamebtn btn-success m-2right/buttonbutton //拿着父组件传过来的参数去调用父组件传的函数onClick{() this.props.onDelete(this.props.id)} classNamebtn btn-danger m-2delete/button/React.Fragment);}getStyles() {let styles {width: 50px,height: 50px,color: white,textAlign: center,lineHeight: 50px,borderRadius: 5px,backgroundColor: lightblue,marginLeft: this.state.x,}if (this.state.x 0) {styles.backgroundColor orange;}return styles;}toString() {const x this.state.x;return x: ${x};}
}
export default Box;页面展示 5每个维护的数据仅能保存在一个this.state中
不要直接修改this.state的值因为setState函数可能会将修改覆盖掉
例如 1.在boxes.jsx中添加一个handleReset函数通过reset按钮触发来将四个子组件的值都变回初始值0id则保持不变。 2.这时会发现当点击reset按钮后打印的boxes中的x值都变成0了但是页面的每个子组件的x渲染还是原来的值。 3.因为通过handleReset函数中的this.setState更新的是this.state.boxes里面的x在实际渲染中是通过box.jsx中的this.state.x来获取而该过程只会在初始化的时候执行一次通过this.props.x传过来的值获取后面每次修改的时候该过程不会重复执行所以不管怎么修改this.props.x的值box.jsx里的x都不会发生变化了因此实际的x渲染不会发生改变。
相当于在两个地方存了同一个数据在一边修改可能无法影响两边的数据
boxes.jsx: import React, { Component } from react;import Box from ./box;class Boxes extends Component {state { boxes: [{id: 1, x: 1},{id: 2, x: 2},{id: 3, x: 3},{id: 4, x: 4},]} handleReset () {const boxes this.state.boxes.map(b {return {id: b.id,x: 0,}});this.setState({boxes});console.log(this.state);}handleDelet (boxId) {console.log(handle delete, boxId);const boxes this.state.boxes.filter(b b.id ! boxId);this.setState({boxes: boxes});}render() { return (React.Fragmentbutton style{{marginBottom: 15px}} classNamebtn btn-infoonClick{this.handleReset}Reset/button{this.state.boxes.map(box (Box key{box.id}x{box.x}//将函数以及函数需要用到的参数传递给子组件id{box.id}onDelete{this.handleDelet} //子组件在获取到onDelete时实际是获取到了父组件的handleDelete方法的指代h1Box:/h1p#{box.id}/p/Box))}/React.Fragment);}
}export default Boxes;box.jsx:
import React, { Component } from reactclass Box extends Component {state { x: this.props.x,};handleClickLeft (step) {this.setState({x: this.state.x - step,});console.log(click left, this.state.x);}handleClickRight (step) { this.setState({x: this.state.x step,});console.log(click right, this.state.x);}render() { return (React.Fragment{this.props.children[0]}div style{this.getStyles()}{this.toString()}/div{this.props.children[1]}button onClick{() this.handleClickLeft(10)} classNamebtn btn-primary m-2left/buttonbutton onClick{() this.handleClickRight(10)} classNamebtn btn-success m-2right/buttonbutton //拿着父组件传过来的参数去调用父组件传的函数onClick{() this.props.onDelete(this.props.id)} classNamebtn btn-danger m-2delete/button/React.Fragment);}getStyles() {let styles {width: 50px,height: 50px,color: white,textAlign: center,lineHeight: 50px,borderRadius: 5px,backgroundColor: lightblue,marginLeft: this.state.x,}if (this.state.x 0) {styles.backgroundColor orange;}return styles;}toString() {const x this.state.x;return x: ${x};}
}
export default Box;页面展示
怎么使得子组件的x渲染能在外面进行修改呢 1.将内部的state.x删除 2.把x放到外部组件的this.state.boxes里面 也就是只存一份x数据然后两边都从这一份数据中读取就不会产生覆盖的问题
如
boxes.jsx:
import React, { Component } from react;import Box from ./box;class Boxes extends Component {state { boxes: [{id: 1, x: 1},{id: 2, x: 2},{id: 3, x: 3},{id: 4, x: 4},]} handleReset () {const boxes this.state.boxes.map(b {return {id: b.id,x: 0,}});this.setState({boxes});console.log(this.state);}handleDelet (boxId) {console.log(handle delete, boxId);const boxes this.state.boxes.filter(b b.id ! boxId);this.setState({boxes: boxes});}handleClickLeft (box) {const boxes [...this.state.boxes];const k boxes.indexOf(box); //在当前boxes数组中找到当前元素的下标boxes[k] {...boxes[k]};boxes[k].x --;this.setState({boxes});//console.log(click left, this.boxes[k].x);}handleClickRight (box) { const boxes [...this.state.boxes];const k boxes.indexOf(box); //在当前boxes数组中找到当前元素的下标boxes[k] {...boxes[k]};boxes[k].x ;this.setState({boxes});//console.log(click right, this.boxes[k].x);}render() { return (React.Fragmentbutton style{{marginBottom: 15px}} classNamebtn btn-infoonClick{this.handleReset}Reset/button{this.state.boxes.map(box (Box key{box.id}//将函数以及函数需要用到的参数传递给子组件id{box.id}box{box}onDelete{this.handleDelet} //子组件在获取到onDelete时实际是获取到了父组件的handleDelete方法的指代onClickLeft{() this.handleClickLeft(box)}onClickRight{() this.handleClickRight(box)}h1Box:/h1p#{box.id}/p/Box))}/React.Fragment);}
}export default Boxes;box.jsx:
import React, { Component } from reactclass Box extends Component {render() { return (React.Fragment{this.props.children[0]}div style{this.getStyles()}{this.toString()}/div{this.props.children[1]}button onClick{() this.props.onClickLeft(10)} classNamebtn btn-primary m-2left/buttonbutton onClick{() this.props.onClickRight(10)} classNamebtn btn-success m-2right/buttonbutton //拿着父组件传过来的参数去调用父组件传的函数onClick{() this.props.onDelete(this.props.id)} classNamebtn btn-danger m-2delete/button/React.Fragment);}getStyles() {let styles {width: 50px,height: 50px,color: white,textAlign: center,lineHeight: 50px,borderRadius: 5px,backgroundColor: lightblue,marginLeft: this.props.box.x,}if (this.props.box.x 0) {styles.backgroundColor orange;}return styles;}toString() {const x this.props.box.x;return x: ${x};}
}
export default Box;
页面展示
6创建App组件
包含
导航栏组件Boxes组件
文件创建快捷缩写自动补全 imrc Tab import React, { Component } from ‘react’; cc Tab
class extends Component {state { } render() { return ();}
}export default ;注意
要将多个组件共用的数据存放到最近公共祖先的this.state中比如我们需要在另外一个组件中维护当前x不为0的组件个数那我们则需要将当前组件Boxes和另外一个组件Navbar放到新建的公共祖先组件App中 这里就需要将维护数据的state全部往上移移动到公共祖先中维护。然后两边的节点通过公共祖先使用props传输获取数据。
app.jsx:
import React, { Component } from react;
import NavBar from ./navbar;
import Boxes from ./boxes;class App extends Component {state { boxes: [{id: 1, x: 1},{id: 2, x: 2},{id: 3, x: 3},{id: 4, x: 4},]} handleReset () {const boxes this.state.boxes.map(b {return {id: b.id,x: 0,}});this.setState({boxes});console.log(this.state);}handleDelet (boxId) {console.log(handle delete, boxId);const boxes this.state.boxes.filter(b b.id ! boxId);this.setState({boxes: boxes});}handleClickLeft (box) {const boxes [...this.state.boxes];const k boxes.indexOf(box); //在当前boxes数组中找到当前元素的下标boxes[k] {...boxes[k]};boxes[k].x --;this.setState({boxes});//console.log(click left, this.boxes[k].x);}handleClickRight (box) { const boxes [...this.state.boxes];const k boxes.indexOf(box); //在当前boxes数组中找到当前元素的下标boxes[k] {...boxes[k]};boxes[k].x ;this.setState({boxes});//console.log(click right, this.boxes[k].x);}render() { return (React.FragmentNavBar boxesCount{this.state.boxes.filter(b b.x ! 0).length}/div classNamecontainerBoxes boxes{this.state.boxes}onReset{this.handleReset}onClickLeft{this.handleClickLeft}onClickRight{this.handleClickRight}onDelete{this.handleDelet}//div/React.Fragment);}
}export default App;navbar.jsx:
import React, { Component } from react;class NavBar extends Component {state { } render() { return (nav classNamenavbar navbar-light bg-lightdiv classNamecontainer-fluida classNamenavbar-brand href /Navbar spanBoxes Count: {this.props.boxesCount} /span/a/div/nav);}
}export default NavBar;boxes.jsx:
import React, { Component } from react;import Box from ./box;class Boxes extends Component {render() { return (React.Fragmentbutton style{{marginBottom: 15px}} classNamebtn btn-infoonClick{this.props.onReset}Reset/button{this.props.boxes.map(box (Box key{box.id}//将函数以及函数需要用到的参数传递给子组件box{box}onDelete{this.props.onDelete}onClickLeft{() this.props.onClickLeft(box)}onClickRight{() this.props.onClickRight(box)}h1Box:/h1p#{box.id}/p/Box))}/React.Fragment);}
}export default Boxes;box.jsx文件则保持不变
页面展示
7无状态函数组件
当组件中没有用到this.state时可以简写为无状态的函数组件使用sfc Tab键补全
const () {return ( );
}export default ;函数的传入参数为props对象
比如上一小节中的boxes.jsx中没有state属性那么可以修改为无状态的函数组件相当于只有render函数
boxes.jsx:
import React, { Component } from react;import Box from ./box;const Boxes (props) {return (React.Fragmentbutton style{{marginBottom: 15px}} classNamebtn btn-infoonClick{props.onReset}Reset/button{props.boxes.map(box (Box key{box.id}//将函数以及函数需要用到的参数传递给子组件box{box}onDelete{props.onDelete}onClickLeft{() props.onClickLeft(box)}onClickRight{() props.onClickRight(box)}h1Box:/h1p#{box.id}/p/Box))}/React.Fragment);
}export default Boxes;同样的navbar.jsx中也是没有state属性的也可以转换成无状态函数组件
navbar.jsx:
import React, { Component } from react;const NavBar (props) {return (nav classNamenavbar navbar-light bg-lightdiv classNamecontainer-fluida classNamenavbar-brand href /Navbar spanBoxes Count: {props.boxesCount} /span/a/div/nav);
}export default NavBar;补充回忆 解构也可以在参数里面解构直接将props解构成参数列表
boxes.jsx:
import React, { Component } from react;import Box from ./box;const Boxes ({onReset, onDelete, onClickLeft, onClickRight, boxes}) {return (React.Fragmentbutton style{{marginBottom: 15px}} classNamebtn btn-infoonClick{onReset}Reset/button{boxes.map(box (Box key{box.id}//将函数以及函数需要用到的参数传递给子组件box{box}onDelete{onDelete}onClickLeft{() onClickLeft(box)}onClickRight{() onClickRight(box)}h1Box:/h1p#{box.id}/p/Box))}/React.Fragment);
}export default Boxes;8组件的生命周期
Mount周期挂载第一次加载执行的函数执行顺序 constructor() - render() - componentDisMount()
示例 app.jsx:
import React, { Component } from react;
import NavBar from ./navbar;
import Boxes from ./boxes;class App extends Component {state { boxes: [{id: 1, x: 1},{id: 2, x: 2},{id: 3, x: 3},{id: 4, x: 4},]} componentDidMount() {console.log(App - Mounted);}constructor() {super();console.log(App - Constructor);}handleReset () {const boxes this.state.boxes.map(b {return {id: b.id,x: 0,}});this.setState({boxes});console.log(this.state);}handleDelet (boxId) {console.log(handle delete, boxId);const boxes this.state.boxes.filter(b b.id ! boxId);this.setState({boxes: boxes});}handleClickLeft (box) {const boxes [...this.state.boxes];const k boxes.indexOf(box); //在当前boxes数组中找到当前元素的下标boxes[k] {...boxes[k]};boxes[k].x --;this.setState({boxes});//console.log(click left, this.boxes[k].x);}handleClickRight (box) { const boxes [...this.state.boxes];const k boxes.indexOf(box); //在当前boxes数组中找到当前元素的下标boxes[k] {...boxes[k]};boxes[k].x ;this.setState({boxes});//console.log(click right, this.boxes[k].x);}render() { console.log(App - rendered);return (React.FragmentNavBar boxesCount{this.state.boxes.filter(b b.x ! 0).length}/div classNamecontainerBoxes boxes{this.state.boxes}onReset{this.handleReset}onClickLeft{this.handleClickLeft}onClickRight{this.handleClickRight}onDelete{this.handleDelet}//div/React.Fragment);}
}export default App;Update周期修改执行顺序 render() - componentDidUpdate() 示例 分别在box.jsx, boxes.jsx, navbar.jsx, app.jsx中的加入componentDidUpdate()函数并在render()和更新函数中加入打印语句观察不同层级之间的执行顺序 结果如下
componentDidUpdate()中还有prevProps,prevState两个参数
componentDidUpdate(prevProps, prevState) {console.log(App - Updated);console.log(prevState, prevState, this.state);}可以通过这两个参数得到上一个状态的state值。
Unmount周期删除执行顺序 componentWillUnmount()
每次删除当前元素时会执行一次 box.jsx:
import React, { Component } from reactclass Box extends Component {// componentDidUpdate() {// console.log(Box - Updated);// }componentWillUnmount() {console.log(Box - Unmount);}render() { //console.log(Box - Rendered);return (React.Fragment{this.props.children[0]}div style{this.getStyles()}{this.toString()}/div{this.props.children[1]}button onClick{() this.props.onClickLeft(10)} classNamebtn btn-primary m-2left/buttonbutton onClick{() this.props.onClickRight(10)} classNamebtn btn-success m-2right/buttonbutton //拿着父组件传过来的参数去调用父组件传的函数onClick{() this.props.onDelete(this.props.box.id)} classNamebtn btn-danger m-2delete/button/React.Fragment);}getStyles() {let styles {width: 50px,height: 50px,color: white,textAlign: center,lineHeight: 50px,borderRadius: 5px,backgroundColor: lightblue,marginLeft: this.props.box.x,}if (this.props.box.x 0) {styles.backgroundColor orange;}return styles;}toString() {const x this.props.box.x;return x: ${x};}
}
export default Box;9.路由
1.Web分类
静态页面页面里的数据是写死的动态页面页面里的数据是动态填充的 后端渲染数据在后端填充前端渲染数据在前端填充
2.安装环境
VsCode安装插件Auto Import - ES6, TS, JSX, TSX安装Route插件npm i react-router-dom
3.Route组件介绍
BrowserRouter:所有需要路由的组件都需要包裹在BrowserRouter组件内Link跳转到某个链接to属性表示跳转到的链接Routes类似于C中的switch匹配第一个路径Route路由path属性表示路径element属性表示路由到的内容
4.URL中传递参数
把URL中的某些部分变成变量读取到组件中。
解析URL
Route path/web/content/:chapter/:section/ element{WebContent/} /类组件获取参数 web.jsx:
import React, { Component } from react;
import { Link } from react-router-dom;class Web extends Component {state { webs: [{id: 1, title: HTML},{id: 2, title: CSS},{id: 3, title: JavaScript},{id: 4, title: 拳皇},{id: 5, title: React},]} render() { return (React.Fragmenth1Web/h1div{this.state.webs.map(web (div key{web.id}Link to{/web/content/${web.id}}{web.id . web.title}/Link/div))}/div/React.Fragment);}
}export default Web;webContent.jsx:
import React, { Component } from react;
import { useParams } from react-router-dom;class WebContent extends Component {state { } render() { console.log(this.props.params);return (React.Fragmenth1Web - {this.props.params.chapter}/h1div内容/div/React.Fragment);}
}export default (props) (WebContent {...props}params{useParams()}/
);
app.jsx:
import React, { Component } from react;
import NavBar from ./navbar;
import Home from ./home;
import Linux from ./linux;
import Django from ./django;
import Web from ./web;
import WebContent from ./webContent;
import NotFound from ./notFound;
import { Route, Routes } from react-router-dom;class App extends Component {state { } render() { return (React.FragmentNavBar /div classNamecontainerRoutesRoute path/ element{Home/} /Route path/linux element{Linux/ } /Route path/django element{Django/ } /Route path/web element{Web/} /Route path/web/content/:chapter/:section/ element{WebContent/} //Routes/div/React.Fragment);}
}export default App;参数打印 5.Search Param 传递参数
web.jsx:
import React, { Component } from react;
import { Link } from react-router-dom;class Web extends Component {state { webs: [{id: 1, title: HTML},{id: 2, title: CSS},{id: 3, title: JavaScript},{id: 4, title: 拳皇},{id: 5, title: React},]} render() { return (React.Fragmenth1Web/h1div{this.state.webs.map(web (div key{web.id}Link to{/web/content?chapter${web.id}}{web.id . web.title}/Link/div))}/div/React.Fragment);}
}export default Web;webContent.jsx:
import React, { Component } from react;
import { useSearchParams } from react-router-dom;
import { Link } from react-router-dom;class WebContent extends Component {state { searchParams: this.props.params[0],setSearchParams: this.props.params[1],};render() { console.log(this.state.searchParams.get(chapter));return (React.Fragmenth1Web - {this.state.searchParams.get(chapter)}/h1div内容/divLink to/web返回/Link/React.Fragment);}
}export default (props) (WebContent {...props}params{useSearchParams()}/
);
app.jsx:
import React, { Component } from react;
import NavBar from ./navbar;
import Home from ./home;
import Linux from ./linux;
import Django from ./django;
import Web from ./web;
import WebContent from ./webContent;
import NotFound from ./notFound;
import { Route, Routes } from react-router-dom;class App extends Component {state { } render() { return (React.FragmentNavBar /div classNamecontainerRoutesRoute path/ element{Home/} /Route path/linux element{Linux/ } /Route path/django element{Django/ } /Route path/web element{Web/} /Route path/web/content element{WebContent/} //Routes/div/React.Fragment);}
}export default App;参数提取
6.重定向
使用Navigate组件可以重定向Navigate是react实现好的组件。
Route path* element{ Navigate replace to/404 / } /app.jsx:
import React, { Component } from react;
import NavBar from ./navbar;
import Home from ./home;
import Linux from ./linux;
import Django from ./django;
import Web from ./web;
import WebContent from ./webContent;
import NotFound from ./notFound;
import { Route, Routes, Navigate} from react-router-dom;class App extends Component {state { } render() { return (React.FragmentNavBar /div classNamecontainerRoutesRoute path/ element{Home/} /Route path/linux element{Linux/ } /Route path/django element{Django/ } /Route path/web element{Web/} /Route path/web/content element{WebContent/} /Route path/404 element{NotFound/} /Route path* element{Navigate replace to/404/} //Routes/div/React.Fragment);}
}export default App;notFound:
import React, { Component } from react;class NotFound extends Component {state { } render() { return (h1NotFound/h1);}
}export default NotFound;7.嵌套路由
linux.jsx:
import React, { Component } from react;
import { Outlet } from react-router-dom;class Linux extends Component {state { } render() { return (React.Fragmenth1Linux/h1hr / Outlet //React.Fragment);}
}export default Linux;app.jsx:
import React, { Component } from react;
import NavBar from ./navbar;
import Home from ./home;
import Linux from ./linux;
import Django from ./django;
import Web from ./web;
import WebContent from ./webContent;
import NotFound from ./notFound;
import { Route, Routes, Navigate} from react-router-dom;class App extends Component {state { } render() { return (React.FragmentNavBar /div classNamecontainerRoutesRoute path/ element{Home/} /Route path/linux element{Linux/ }Route pathhomework element{h4homework/h4}/RouteRoute pathterminal element{h4terminal/h4}/RouteRoute path* element{h4其他/h4}/Route/RouteRoute path/django element{Django/ } /Route path/web element{Web/} /Route path/web/content element{WebContent/} /Route path/404 element{NotFound/} /Route path* element{Navigate replace to/404/} //Routes/div/React.Fragment);}
}export default App;页面展示
注意需要在父组件中添加Outlet /组件用来填充子组件的内容。 10.Redux
redux将所有数据存储到树中并且树是唯一的。
原本的DOM树中比如两个组件之间的公共数据需要修改时A组件修改数据使得B组件发生相应变化需要将state数据提取到两个组件的最近公共祖先然后两个组件同时绑定公共祖先内的state对应的数据当层级较多时这个关系维护起来就很麻烦 比如这里想要在1号组件内修改数据使得2号组件的状态发生改变就需要一步一步往上提取到APP中整个公共数据的维护就很冗余和麻烦。 Redux的作用就是在整个DOM树之外用一个专门的地方存放类似于全局变量的数据也就是将不同组件之间可能需要交互的数据放到全局变量中。当两个组件需要交互时就只需要这两个组件单独向该组件访问即可。
1Redux基本概念
store存储树结构state维护的数据一般维护成树的结构。reducer 对state进行更新的函数每个state绑定一个reducer。传入两个参数当前state和action返回新的state。action一个普通对象存储reducer的传入参数一般描述对state的更新类型。dispatch传入一个参数action对整颗state树操作一遍。 当我们想要修改某个state的值时会递归调用所有的reducer同时根据action中对state的更新类型作对比如果是当前节点对应类型则会更新当前节点的state的值。 2React-Redux基本概念
Provider:用来包裹整个项目其store属性用来存储redux的store对象connect(mapStateToProps, mapDispatchToProps)函数用来将store与组件关联起来。 mapStateToProps每次store中的状态更新后调用一次用来更新组件中的值mapDispatchToProps组件创建时调用一次用来将store的1dispatch函数传入组件。
redux相关依赖安装
npm i redux react-redux reduxjs/toolkit3快速入门
index.js:
import React from react;
import ReactDOM from react-dom/client;
import ./index.css;
import { configureStore } from reduxjs/toolkit;const f1 (state 1, action) {switch(action.type) {//规定对应类型执行怎么样的state更新操作case multiple:return state * 2;case cut:return state - 1;default:return state; }
};
const store configureStore({reducer: f1
});console.log(native value: ${store.getState()});//调用dispatch时传入action对象对象中指定type从而实现对应的更新
store.dispatch({type:multiple});
store.dispatch({type:multiple}); //多次调用则多次更新console.log(after update: ${store.getState()});const root ReactDOM.createRoot(document.getElementById(root));
root.render(React.StrictMode/React.StrictMode
);
state打印 subscribe:
//每次dispatch更新之后会调用一次
store.subscribe(() {console.log(after update: ${store.getState()})}) import React from react;
import ReactDOM from react-dom/client;
import ./index.css;
import { configureStore } from reduxjs/toolkit;const f1 (state 1, action) {switch(action.type) {//规定对应类型执行怎么样的state更新操作case multiple:return state * 2;case cut:return state - 1;default:return state; }
};
const store configureStore({reducer: f1
});console.log(native value: ${store.getState()});//调用dispatch时传入action对象对象中指定type从而实现对应的更新//每次dispatch更新之后会调用一次
store.subscribe(() {console.log(after update: ${store.getState()})}) store.dispatch({type:multiple});
store.dispatch({type:multiple}); //多次调用则多次更新
store.dispatch({type:cut});
store.dispatch({type:cut});const root ReactDOM.createRoot(document.getElementById(root));
root.render(React.StrictMode/React.StrictMode
); 参数传入
import React from react;
import ReactDOM from react-dom/client;
import ./index.css;
import { configureStore } from reduxjs/toolkit;const f1 (state 1, action) {switch(action.type) {//规定对应类型执行怎么样的state更新操作case multiple:return state * action.value;case cut:return state - action.value;default:return state; }
};
const store configureStore({reducer: f1
});console.log(native value: ${store.getState()});//调用dispatch时传入action对象对象中指定type从而实现对应的更新//每次dispatch更新之后会调用一次
store.subscribe(() {console.log(after update: ${store.getState()})}) store.dispatch({type:multiple, value: 3});
store.dispatch({type:multiple, value: 2}); //多次调用则多次更新
store.dispatch({type:cut, value: 5});
store.dispatch({type:cut, value: 3});const root ReactDOM.createRoot(document.getElementById(root));
root.render(React.StrictMode/React.StrictMode
); 子节点的状态更新
1.手动实现
具体看代码注释
import React from react;
import ReactDOM from react-dom/client;
import ./index.css;
import { configureStore } from reduxjs/toolkit;const f1 (state 1, action) {switch(action.type) {//规定对应类型执行怎么样的state更新操作case multiple:return state * action.value;case cut:return state - action.value;default:return state; }
};const f2 (state , action) {switch(action.type) {case concat:return state action.character;default:return state;}
};//将两个state作为f3的子节点
const f3 (state {}, action) {return {f1: f1(state.f1, action),f2: f2(state.f2, action),}
}//以f3作为树根
const store configureStore({reducer: f3
});console.log(native value: ${store.getState()});//调用dispatch时传入action对象对象中指定type从而实现对应的更新//每次dispatch更新之后会调用一次
store.subscribe(() {console.log(store.getState())}) store.dispatch({type:multiple, value: 3});
store.dispatch({type:multiple, value: 2});
store.dispatch({type:cut, value: 5});
store.dispatch({type:cut, value: 3});//同样是调用树根的值传入type实现不同子节点的状态更新
store.dispatch({type: concat, character: this is concat result })
store.dispatch({type: concat, character: this is concat result })const root ReactDOM.createRoot(document.getElementById(root));
root.render(React.StrictMode/React.StrictMode
); 2.API实现
combineReducers: 直接使用该API效果和上面手动实现的效果一致。 import React from react;
import ReactDOM from react-dom/client;
import ./index.css;
import { configureStore } from reduxjs/toolkit;
import { combineReducers } from reduxjs/toolkit;const f1 (state 1, action) {switch(action.type) {//规定对应类型执行怎么样的state更新操作case multiple:return state * action.value;case cut:return state - action.value;default:return state; }
};const f2 (state , action) {switch(action.type) {case concat:return state action.character;default:return state;}
};// //将两个state作为f3的子节点
// const f3 (state {}, action) {
// return {
// f1: f1(state.f1, action),
// f2: f2(state.f2, action),
// }
// }const f3 combineReducers({f1: f1,f2: f2,
});//以f3作为树根
const store configureStore({reducer: f3
});console.log(native value: ${store.getState()});//调用dispatch时传入action对象对象中指定type从而实现对应的更新//每次dispatch更新之后会调用一次
store.subscribe(() {console.log(store.getState())}) store.dispatch({type:multiple, value: 3});
store.dispatch({type:multiple, value: 2});
store.dispatch({type:cut, value: 5});
store.dispatch({type:cut, value: 3});//同样是调用树根的值传入type实现不同子节点的状态更新
store.dispatch({type: concat, character: this is concat result })
store.dispatch({type: concat, character: this is concat result })const root ReactDOM.createRoot(document.getElementById(root));
root.render(React.StrictMode/React.StrictMode
); 4实战演练
实现一个app
包含两个组件number和string在number组件中可以通过添加按钮给string组件中的内容添加指定的内容在string组件中可以通过加或者减按钮给number组件中的x增加或者减去指定内容 分析首先使用redux去维护每个组件的state值通过传入action对象指定操作类型从而实现指定组件的state更新需要将state值从redux中取出并显示在组件中访问存储的全局变量通过connect(mapStateToProps)将store与组件关联起来。每次store中的状态更新后调用一次用来更新组件中的值。按下按钮后修改对应组件中的state值也就是如何更新全局变量
说明
mapStateToProps将state映射到props中用来更新组件中的值mapDispatchToProps将dispatch函数映射到props中用来操作改变组件中的值
1.index.js:
import React from react;
import ReactDOM from react-dom/client;
import ./index.css;
import { configureStore } from reduxjs/toolkit;
import { combineReducers } from reduxjs/toolkit;
import App from ./components/app;import { Provider } from react-redux;const f1 (state 0, action) {switch(action.type) {//规定对应类型执行怎么样的state更新操作case add:return state action.value;case cut:return state - action.value;default:return state; }
};const f2 (state the concat string is:, action) {switch(action.type) {case concat:return state action.character;default:return state;}
};}const f3 combineReducers({number: f1,string: f2,
});//以f3作为树根
const store configureStore({reducer: f3
});
const root ReactDOM.createRoot(document.getElementById(root));
root.render(Provider store{store}App //Provider
);
2.app.jsx:
import React, { Component } from react;
import Number from ./number;
import String from ./string;class App extends Component {state { } render() { return (React.FragmentNumber /hr /String //React.Fragment);}
}export default App;3.number.jsx:
import React, { Component } from react;
import { connect } from react-redux;class Number extends Component {state { } handleClick () {this.props.concat(y );console.log(click concat);}render() { console.log(this.props);return (React.Fragmenth3Nubmer:/h3div{this.props.number}/divbutton onClick{this.handleClick}添加/button/React.Fragment);}
}//将state绑定到参数中
const mapStateToProps (state, props) {return {number: state.number,}
}//将dispatch函数绑定到props上
const mapDisPatchToProps {concat: (c) {return {type: concat,character: c,}}
}export default connect(mapStateToProps, mapDisPatchToProps)(Number);4.string.jsx:
import React, { Component } from react;
import { connect } from react-redux;class String extends Component {state { } handleClickAdd () {this.props.add(10);console.log(click add);}handleClickCut () {this.props.cut(1);console.log(click cut);}render() { return (React.Fragmenth3String:/h3div{this.props.string}/divbutton onClick{this.handleClickAdd}加/buttonbutton onClick{this.handleClickCut}减/button/React.Fragment);}
}const mapStateToProps (state, props) {return {string: state.string,};
};const mapDisPatchToProps {add: (c) {return {type: add,value: c,}},cut: (c) {return {type: cut,value: c,}}
};export default connect(mapStateToProps, mapDisPatchToProps)(String);效果展示
过程解释
整个流程分为两步以string的操作为例 1.数值的改变 点击添加按钮 - 调用handleClick函数 - handleClick函数触发this.props.concat通过mapDispatchToProps将concat绑定到props上- concat函数会返回一个对象包含typeconcat和参数c - 通过connect(mapDispatchToProps)会传递到所有节点的reducer上这里concat返回的对象就会作为action传入reducer中对于f1来说会对比type发现均不匹配所以无操作对于f2来说type与concat匹配于是会执行对应的state操作 - 完成f2的操作在原本的state值上添加传入的参数字符 2.数值的更新同步 store是根节点由于store传入到了Provider组件中,于是会重新渲染整个Provider组件- 重新渲染App组件 - String组件 - 通过mapStateToProps已经将f3中的string代表的state绑定到了String组件的props中 - 通过this.props.string获取到更新之后的state - 完成同步
注意这里不管调用那个组件的参数更新都会将所有的reducer执行一遍 11. React实战项目——计算器
总体效果预览
1.创建项目以及配置环境
1.创建appcreate-react-app calculator-app2.配置环境 配置redux用于组件之间的交互npm i redux react-redux reduxjs/toolkit配置路由router: npm i react-router-dom配置bootstrap前端常用样式库npm i bootstrap文件中引入bootstrapimport bootstrap/dist/css/bootstrap.css; 3.启动项目进入calculator-app文件夹运行git bash执行npm start
2.创建各个组件以及建立路由
需要用到路由的组件必须: 引入import { BrowserRouter } from react-router-dom;将该组件使用BrowserRouter组件包裹起来比如:
BrowserRouterApp /
/BrowserRouter1.url的切换
创建navbar.jsx来实现各个组件之间的url的转换
1.在bootstrap官网找到合适的样式并复制过来将class改为className2.添加并修改对应的内容使得符合预期样式 3.写路由引入Linkimport { Link } from react-router-dom;4.将所有需要路由跳转的地方用Link包围to属性写上跳转的url地址比如to/login表示会跳转到localhost:3000/login
navbar.jsx:
import React, { Component } from react;
import { Link } from react-router-dom;class NavBar extends Component {state { } render() { return (nav classNamenavbar navbar-expand-lg bg-body-tertiarydiv classNamecontainerLink classNamenavbar-brand to/应用/Linkbutton classNamenavbar-toggler typebutton data-bs-togglecollapse data-bs-target#navbarText aria-controlsnavbarText aria-expandedfalse aria-labelToggle navigationspan classNamenavbar-toggler-icon/span/buttondiv classNamecollapse navbar-collapse idnavbarTextul classNamenavbar-nav me-auto mb-2 mb-lg-0li classNamenav-itemLink classNamenav-link active aria-currentpage to/首页/Link/lili classNamenav-itemLink classNamenav-link to/calculator计算器/Link/li/ulul classNamenavbar-navli classNamenav-itemLink classNamenav-link active aria-currentpage to/login登录/Link/lili classNamenav-itemLink classNamenav-link toregister注册/Link/li/ul/div/div/nav);}
}export default NavBar;2.页面跳转
新建components文件夹并在文件夹中依次创建home.jsx, login.jsx, register.jsx, notFound.jsx, calculator.jsx 创建出Home, Login, Register, Calculator, NotFound五个组件并导入到app.jsx中
导入Routes和Route: import { Route, Routes, Navigate } from react-router-dom;
其中如果不属于定义的url则需要实现重定向到404url展示NotFound组件
Route path* element{ Navigate replace to/404 / } /
app.jsx:
import React, { Component } from react;
import NavBar from ./navbar;
import { Route, Routes, Navigate } from react-router-dom;
import Home from ./content/home;
import Login from ./content/login;
import Register from ./content/register;
import Calculator from ./content/calculator;
import NotFound from ./content/notFound;class App extends Component {state { } render() { return (React.FragmentNavBar /div classNamecontainerRoutesRoute path/ element{ Home / } /Route path/login element{ Login / } /Route path/register element{ Register / } /Route path/calculator element{ Calculator / } /Route path/404 element{ NotFound / } /Route path* element{ Navigate replace to/404 / } //Routes/div/React.Fragment);}
}export default App;3.组合组件以及Redux应用
这的计算器实现主要由calculator.jsx来完成计算器前端显示以及数据的交互
calculator.jsx:
import React, { Component } from react;
import Base from ./base;
import { connect } from react-redux;
import NumberButton from ./calculator/numberButton;
import OperationButton from ./calculator/operationButton;
import ACTIONS from ../../redux/action;class Calculator extends Component {state { formater: Intl.NumberFormat(en-us)};format number {if (number ) return ;//将一个数分为整数部分和小数部分const [integer, decimal] number.split(.);//如果小数部分不存在就只返回整数部分的formatif (decimal undefined) {return this.state.formater.format(integer);}//否则的话整数部分format小数部分保持不变return ${this.state.formater.format(integer)}.${decimal};}render() { return (Basediv classNamecalculatordiv classNamescreendiv classNameup-screen{this.format(this.props.lastResult)} {this.props.operation}/divdiv classNamedown-screen{this.format(this.props.currentResult)}/div/divbutton classNamebutton-AC onClick{this.props.clear}AC/buttonbutton onClick{this.props.delete_number}Del/buttonOperationButton operation{÷} / NumberButton classNamebutton-number number{7} /NumberButton classNamebutton-number number{8} /NumberButton classNamebutton-number number{9} /OperationButton operation{×} / NumberButton classNamebutton-number number{4} /NumberButton classNamebutton-number number{5} /NumberButton classNamebutton-number number{6} /OperationButton operation{-} /NumberButton classNamebutton-number number{1} /NumberButton classNamebutton-number number{2} /NumberButton classNamebutton-number number{3} /OperationButton operation{} /NumberButton classNamebutton-number number{0} /NumberButton classNamebutton-number number{.} /button classNamebutton-equal onClick{this.props.evaculate}/button/div/Base);}
}const mapStateToProps (state, props) {return {currentResult: state.currentResult,lastResult: state.lastResult,operation: state.operation,}
}const mapDispatchToProps {delete_number: () {return {type: ACTIONS.DELETE_NUMBER,}},clear: () {return {type: ACTIONS.CLEAR,}},evaculate: () {return {type: ACTIONS.EVACULATE,}}
}export default connect(mapStateToProps, mapDispatchToProps)(Calculator);1.在index.css中定义按钮以及计算器页面样式
body {margin: 0;font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue,sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;
}code {font-family: source-code-pro, Menlo, Monaco, Consolas, Courier New,monospace;
}* {box-sizing: border-box;
}.calculator {display: grid;grid-template-columns: repeat(4, 6rem);grid-template-rows: minmax(6rem, auto) repeat(5, 4rem);gap: 1px;background-color: rgba(191, 191, 191, 0.75);width: calc(24rem 5px);margin: 0 auto;border: 2px solid black;}.button-AC {grid-column: 1 / span 2;
}
.button-equal {grid-column: 3 / span 2;
}.button-number {background-color: white
}.screen {grid-column: 1 / span 4;display: flex;flex-direction: column;align-items: flex-end;justify-content: space-around;padding: 10px;word-wrap: break-word;word-break: break-all;
}.up-screen {font-size: 1rem;
}.down-screen {font-size: 3rem;
}.calculator button:hover {background-color: #b5b5b5;
}2.组合Components:
1数字按键组件numberButton
对于数字键来说按下数字键就会将数字键对应的数字添加到输入结果中于是将他们的逻辑全部抽取成一个NumberButton组件 numberButton.jsx: import React, { Component } from react;
import ACTIONS from ../../../redux/action;
import { connect } from react-redux;//将点击按钮对应的内容添加到对应的状态上
class NumberButton extends Component {state { } render() { return (//当点击这个按钮时会获取这个按钮的内容作为参数传入add_number中button onClick{() this.props.add_number(this.props.number)}{this.props.number}/button);}
}const mapDispatchToProps {//add_number已经绑定到Dispatch中,会将对应的type传到reducer中进行匹配add_number: number {return {type: ACTIONS.ADD_NUMBER,number: number,}}
}export default connect(null, mapDispatchToProps)(NumberButton);2运算符按键组件operationButton
对于加减乘除四个按键来说按下时也就是执行对应的运算逻辑所以也可以统一抽取为一个组件operationButton:
operationButton.jsx:
import React, { Component } from react;
import ACTIONS from ../../../redux/action;
import { connect } from react-redux;class OperationButton extends Component {state { } render() { return (button onClick{() {this.props.choose_operation(this.props.operation)}}{this.props.operation}/button);}
}const mapDispatchToProps {choose_operation: operation {return {type: ACTIONS.CHOOSE_OPERATION,operation: operation,}}
}export default connect(null, mapDispatchToProps)(OperationButton);最后只需要将对应的按键用组件进行包围即可完成高效的复用。
3.Redux使用五个行为维护四个状态
1创建store树根
store.js:
import { configureStore } from reduxjs/toolkit;
import reducer from ./reducer;const store configureStore({reducer: reducer,
});export default store;2定义静态的行为集合
action.js:
const ACTIONS {ADD_NUMBER: add-number,DELETE_NUMBER: delete-number,CHOOSE_OPERATION: choose-operation,CLEAR: clear,EVACULATE: evaculate
};export default ACTIONS;3reducer触发每个状态的更新
reducer.js: import ACTIONS from ./action;const evaluate state {let {lastResult, operation, currentResult} state;let last parseFloat(lastResult);let cur parseFloat(currentResult);let res 0;switch(operation) {case :res last cur;break;case -:res last - cur;break;case ÷:res last / cur;break;case ×:res last * cur;break; }return res.toString();
}const reducer (state{currentResult: ,lastResult: ,operator: ,overwrite: false, //用于判断当前输入结果是否需要覆盖默认为false只有按下等号得到结果之后再输入内容才需要覆盖
}, action) {switch(action.type) {case ACTIONS.ADD_NUMBER://如果overwrite为true则说明这是在计算结果之后输入数字//需要将计算结果清空,输入结果显示为当前输入的数字if (state.overwrite) { return {...state,currentResult: action.number,overwrite: false,}}//如果当前的显示为0并且当前位也为0则说明是00则不添加if (state.currentResult 0 action.number 0) {return state;}//当前结果为0如果下一位不是.构成0.x的话说明是0x直接替换掉0变成xif (state.currentResult 0 action.number ! .) {return {...state,currentResult: action.number,}}//如果输入的结果是包含.的则再次点击.则不添加if (action.number . state.currentResult.includes(.)) {return state;}//如果当前输入的结果直接是.则需要在前面补上0变成0.if (action.number . state.currentResult ) {return {...state,currentResult: 0 action.number,}}return {...state,currentResult: state.currentResult action.number,}case ACTIONS.DELETE_NUMBER://当得到计算结果再按del键时应该将计算结果作为一个整体删除而不是一位一位删除if (state.overwrite) {return {...state,currentResult: ,overwrite: false,}}//当前输入结果为空时就不需要再删除了if (state.currentResult ) {return state;}return {...state,//slice(0, -1):删除从0开始的-1个元素也就是把这个结果的最后一个元素删除currentResult: state.currentResult.slice(0, -1),}case ACTIONS.CHOOSE_OPERATION://如果上一个计算结果为0并且当前的输入结果也是空的则不添加if (state.lastResult state.currentResult ) {return state;}//当上面没有结果并且输入结果不为空但是按了运算符时// 需要将当前结果发送到上面的结果中并且运算符变为当前按下的运算符//当前输入结果清空if (state.lastResult ) {return {//这里...state会将state解构出来然后后面列举的值会被替换成指定的内容...state,lastResult: state.currentResult,operation: action.operation,currentResult: ,}}//当当前输入结果为空再次点击运算符时表示需要将运算符替换掉if (state.currentResult ) {return {...state,operation: action.operation}}//最后就是其他情况//当上一个计算结果不为空并且当前输入结果不为空// 再次点击运算符就需要将 上一个计算结果 运算符 当前输入结果 最终结果//放入到上一个计算结果中并且将当前按下的运算符放到对应位置return {...state,lastResult: evaluate(state),operation: action.operation,currentResult: }case ACTIONS.CLEAR:return {...state,lastResult: ,operation: ,currentResult: }case ACTIONS.EVACULATE:if (state.currentResult ||state.operation || state.lastResult )return state;return {...state,currentResult: evaluate(state),lastResult: ,operation: ,overwrite: true,}default:return state;}
};export default reducer;12.总结
上述只是支离破碎的片段可能观感不佳请谅解完整项目放在acgit仓库 *注以上内容来自acwing平台的web应用课学习整理仅作为学习交流不作为商业用途如有侵权联系删除。