1.react是什么
用于构建用户界面的JavaScript库,将数据渲染为HTML视图的开源javaScript库
2.为什么要学react
1.原生的JavaScript操作DOM繁琐,效率低。
2.使用原生的JavaScript直接操作DOM,浏览器会进行大量的重新绘制,重新排序。
3.原生JavaScript没有组件化编码方案,代码复用率低。
3.React的特点
1.采用组件化模式、声明式编码,提高开发效率及组件复用率。
2.在React Native中可以使用React语法进行移动端开发。
3.使用虚拟DOM和优秀的Diffing算法,尽量减少与真实DOM的交互。
4.学之前要准备好四个js包
1.babel.js(1.用于ES6转ES5。2.对引入语句import浏览器识别不了进行转换。3.jsx转js)
2.react.development.js (react核心库)
3.react-dom.development.js (react扩展库,用于react帮你操作dom)
4.prop-types.js (react的传参类型库)
5.入门React
1.jsx创建虚拟DOM
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<!-- 1.准备一个容器 -->
<div id="root"></div>
<!-- 2.引入react核心库 -->
<script src="../依赖包/旧版本/react.development.js"></script>
<!-- 3.引入react-dom,用于支持react操作dom -->
<script src="../依赖包/旧版本/react-dom.development.js"></script>
<!-- 4.引入babel,用于将jsx转为js -->
<script src="../依赖包/旧版本/babel.min.js"></script>
<!-- 5.默认是text/javascript,但现在写的不是js,是jsx所以要用babel -->
<script type="text/babel">
// 因为react是操作虚拟dom所以第一步
// 1.创建一个虚拟dom
const VDOM=<h1>Hello,React</h1>
//2.渲染虚拟dom到页面
ReactDOM.render(VDOM,document.getElementById('root'))
</script>
</body>
</html>
2.js创建虚拟dom
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>js创建虚拟DOM</title>
</head>
<body>
<!-- 1.准备一个容器 -->
<div id="root"></div>
<!-- 2.引入react核心库 -->
<script src="../依赖包/旧版本/react.development.js"></script>
<!-- 3.引入react-dom,用于支持react操作dom -->
<script src="../依赖包/旧版本/react-dom.development.js"></script>
<script type="text/javascript">
// 因为react是操作虚拟dom所以第一步
// 1.创建一个虚拟dom
//React.createElement()创建虚拟dom,三个参数 1.标签名 2.标签属性 3.标签内容
const VDOM=React.createElement('h1',{id:'title'},'hellow React')
//2.渲染虚拟dom到页面
ReactDOM.render(VDOM,document.getElementById('root'))
</script>
</body>
</html>
为啥要用jsx不用js?
因为js创建多个层级嵌套的dom结构太繁琐
如果想在h1里加个span标签需要在内容体里在来一遍React.createElement,而jsx直接在创建虚拟DOM的h1里面写一下就行了
jsx
js
jsx语法是js语法的语法糖,jsx执行后就会变成js这种,但是为了我们不用直接写
什么是虚拟DOM?
1.他是一个object类型的对象(一般对象)
2.虚拟DOM比较"轻",真实DOM比较"重",因为虚拟DOM是React内部在用,无需真实DOM上那么多属性。
3.虚拟DOM最终会被React转化为真实DOM,呈现在页面上。
什么是jsx?
1.全称是JavaScript XML。
2.react定义的一中类似于XML的js扩展语法:js+XML。
3.本质是React.createElement()方法的语法糖。
4.用来简化创建虚拟DOM。
jsx语法规则
1.定义虚拟dom时不要加引号
2.标签中混入js表达式时要加{}。
3.样式的指定类名不能用class,要用className。(小知识:因为jsx要区分ES6的类class,所以用className)
4.行类样式要采用style={{key:value}}的形式去写。(fontSize:"20px",过长的要用驼峰)
5.虚拟DOM必须只有一个根标签。
6.标签必须闭合。
7.标签首字母。
(1)若小写字母开头,则会转化为html的同名元素,若html中没该标签同名元素,则报错。
(2)若大写开头,react就去渲染对应的组件,若没有定义则报错。
JSX小练习(循环)
这里为什么用map,因为{}里接受的是js表达式
表达式:就是你拿这个变量接,有返回值的
组件
函数式(简单)组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<!-- 1.准备一个容器 -->
<div id="root"></div>
<script src="../依赖包/旧版本/react.development.js"></script>
<script src="../依赖包/旧版本/react-dom.development.js"></script>
<script src="../依赖包/旧版本/babel.min.js"></script>
<script type="text/babel">
function MyComponent(){
console.log(this)
return (
<div>
欢迎来到函数式组件
</div>
)
}
//2.渲染虚拟dom到页面
ReactDOM.render(<MyComponent />,document.getElementById('root'))
</script>
</body>
</html>
关于this
一般来说定义的函数在window里调用this指的是window,但此处的this是undefined,因为babel编译后开启了严格模式,禁止this指向window。
执行了ReactDOM.render(<MyComponent />,document.getElementById('root'))之后发生了什么?
1.React解析组件标签,找到MyComponent组件
2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转化为真是DOM,随后呈现在页面中。
关于类
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
//创建一个Person类
class Person {
//构造器方法
constructor(name, age) {
//构造器的this是new的实例对象,p1 new的就是p1
this.name = name
this.age = age
}
speak() {
//speak方法放在哪里?放在了调用者原型的上,供调用者使用
//通过Person实例调用speak时,speak中的this就是Person实例,不能说speak中的this就是Person实例因为通过apply,bind,call调用是可以更改this指向的
console.log(`他的名字是${this.name},他今年${this.age}`);
}
}
//创建一个Person的实例对象
const p1 = new Person('哪吒', 15)
const p2 = new Person('申公豹', 30)
p1.speak()
console.log(p1.__proto__.speak); //在原型上看speak方法
p2.speak.call({ name: '小明', age: 30 })
console.log(p2);
//创建一个Student类继承Person
class Student extends Person {
constructor(name, age, skill) {
super(name, age)//当父类有共同属性时必须要加super(super帮你调用父类的构造器)
this.skill = skill
}
}
const s1 = new Student('小猪熊', 4, '唱歌')
console.log(s1, 9);
//总结
//类里面的构造器不是一定要写的,要对进行一些初始化操作,如添加指定属性时才写
//如果A类继承了B类,且A类里有构造器constructor,那么A类构造器里一定要写super来调用
//类里所定义的方法都是放在类的原型对象上,供调用者使用
</script>
</body>
</html>
类式组件(复杂组件)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>类式组件</title>
</head>
<body>
<!-- 1.准备一个容器 -->
<div id="root"></div>
<script src="../依赖包/旧版本/react.development.js"></script>
<script src="../依赖包/旧版本/react-dom.development.js"></script>
<script src="../依赖包/旧版本/babel.min.js"></script>
<script type="text/babel">
//创建类式组件
class MyComponent extends React.Component{
//render 这里的render和外面的render是不一样的,这里的render放在类(MyComponent)的原型对象上,供实例使用
render(){
return(
<div>
欢迎来到类式组件
</div>
)
}
}
//2.渲染虚拟dom到页面
ReactDOM.render(<MyComponent />,document.getElementById('root'))
</script>
</body>
</html>
render里的this是,类(MyComponent)的实例对象
打印this,类里的方法都源于继承的React.Component
既然说rander的方法在实例上,但没有调用new调用这个实例我们把目光放在React.render上
执行了ReactDOM.render(<MyComponent />,document.getElementById('root'))之后发生了什么?
1.React解析组件标签,找到MyComponent组件
2.发现组件是使用类定义的,随后new出该类的实例,并通过实例调用原型上的render方法,将render返回的虚拟DOM转化为真实DOM,呈现在页面上。
对以上简单和复杂组件解释
简单组件没有state,复杂组件有state,(当然后期函数式组件引进useState)
这个state的是组件的实例对象上的,函数组件里连自己的this都没有所以接下来的组件实例的三大属性就去除了函数式组件。
组件的三大属性
1.state及事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>类式组件</title>
</head>
<body>
<div id="root"></div>
<script src="../依赖包/旧版本/react.development.js"></script>
<script src="../依赖包/旧版本/react-dom.development.js"></script>
<script src="../依赖包/旧版本/babel.min.js"></script>
<script type="text/babel">
//创建类式组件
class MyComponent extends React.Component {
//初始化状态
constructor(props) {
super(props)
this.state = {
weather: true
}
}
render() {
const { weather } = this.state
return (
<div>
<button onClick={btn}>点击切换天气</button>
<span>今天天气很{weather ? '凉爽' : '炎热'}。</span>
</div>
)
}
}
function btn() {
console.log(111);
}
//2.渲染虚拟dom到页面
ReactDOM.render(<MyComponent />, document.getElementById('root'))
</script>
</body>
</html>
这里button上的onClick绑定的事件为什么要加花括号?
因为是jsx语法不加括号是字符串
这里button上的onClick绑定的事件后面为什么不像原生那样btn加()?
因为react在渲染组件的时候,帮你new了一个MyComponent实例,通过实例调用到里面的render方法想得到返回值就要执行return里面的代码,然后你加了括号,就是函数调用的方式,把demo这个函数的返回值交给onClick,第一次执行是因为函数的调用然后react就直接执行了btn这个方法,你还没点就打印了,后面点击没用是因为demo的返回值是undefined,去掉括号就是把demo这个函数交给onClick作为回调,等到你点击帮你调dome实现点击事件。
但上面那么写太繁琐且写在类的外面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>类式组件</title>
</head>
<body>
<div id="root"></div>
<script src="../依赖包/旧版本/react.development.js"></script>
<script src="../依赖包/旧版本/react-dom.development.js"></script>
<script src="../依赖包/旧版本/babel.min.js"></script>
<script type="text/babel">
//创建类式组件
class MyComponent extends React.Component {
//初始化状态
constructor(props) {
super(props)
this.state = {
weather: true
}
}
render() {
const { weather } = this.state
return (
<div>
<button onClick={this.btn}>点击切换天气</button>
<span>今天天气很{weather ? '凉爽' : '炎热'}。</span>
</div>
)
}
btn() {
console.log(this);
}
}
//2.渲染虚拟dom到页面
ReactDOM.render(<MyComponent />, document.getElementById('root'))
</script>
</body>
</html>
为什么这一块button上面用this.btn
因为btn这个方法在实例MyComponent的原型上,不加this是直接调用找不到。
听到这有点困惑,为什么render在原型上里面的this是这个类的实例,但btn里的this不行?
因为render是在创建组件时,类的实例直接调用的,所以有this
为什么这里打印this为undefined
因为类里所定义的方法,在内部默认开启了严格模式('use strict'),再加上btn这个函数不是通过实例调用的而是作为onClick的回调,直接调用的,这也就导致this为undefined。
解决:this为undefined
这是一个赋值语句,首先构造器的this是类的实例对象,先从自身(实例对象)找btn这个函数,自身(实例对象)没有再从原型上找到this.btn这个方法把他等于this.btn.bind(this),bind:做了两件事,1.创建了一个新的函数,2.更改这个函数的this指向,这么一来构造器里的this是类的实例对象,然后就创建了一个新的btn,并把这个btn放在实例对象上,这时候你访问this.btn读取的是实例对象上的btn,既然是实例对象上的btn,那么就有实例的this了。
接下来切换天气
更新动态要用this.setState,且是一种合并
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>类式组件</title>
</head>
<body>
<div id="root"></div>
<script src="../依赖包/旧版本/react.development.js"></script>
<script src="../依赖包/旧版本/react-dom.development.js"></script>
<script src="../依赖包/旧版本/babel.min.js"></script>
<script type="text/babel">
//创建类式组件
class MyComponent extends React.Component {
//初始化状态
constructor(props) {
super(props)
this.state = {
weather: true
}
this.btn = this.btn.bind(this)
}
render() {
const { weather } = this.state
return (
<div>
<button onClick={this.btn}>点击切换天气</button>
<span>今天天气很{weather ? '凉爽' : '炎热'}。</span>
</div>
)
}
btn() {
let weather = this.state.weather
this.setState({ weather: !weather })
}
}
//2.渲染虚拟dom到页面
ReactDOM.render(<MyComponent />, document.getElementById('root'))
</script>
</body>
</html>
现在问题构造器执行了几次 答案1
render执行了 1+n次
执行顺序先构造器,构造器调完了实例出来,实例出来了才能调render
然后setState执行完后,会去更新render达到数据在视图上更新。
简写方式(省略构造器)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>类式组件</title>
</head>
<body>
<div id="root"></div>
<script src="../依赖包/旧版本/react.development.js"></script>
<script src="../依赖包/旧版本/react-dom.development.js"></script>
<script src="../依赖包/旧版本/babel.min.js"></script>
<script type="text/babel">
//创建类式组件
class MyComponent extends React.Component {
state = {
weather: true,
}
render() {
const { weather, num } = this.state
return (
<div>
<button onClick={this.btn}>点击切换天气</button>
<span>今天天气很{weather ? '凉爽' : '炎热'}。</span>
</div>
)
}
btn = () => {
let weathers = this.state.weather
this.setState({ weather: !weathers })
}
}
//2.渲染虚拟dom到页面
ReactDOM.render(<MyComponent />, document.getElementById('root'))
</script>
</body>
</html>
2.props (用于接收组件传过来的值)
传参简写
给props添加接参类型(写在类的外面,用实例加)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>props的参数限制</title>
</head>
<body>
<div id="root"></div>
<div id="root2"></div>
<script src="../依赖包/旧版本/react.development.js"></script>
<script src="../依赖包/旧版本/react-dom.development.js"></script>
<script src="../依赖包/旧版本/babel.min.js"></script>
<!-- 引入类型包 -->
<script src="../依赖包/旧版本/prop-types.js"></script>
<script type="text/babel">
class MyComponent extends React.Component {
state = {
weather: '晴天',
}
render() {
const { weather } = this.state
const { name, age, sex,speak } = this.props
return (
<div>
<ul>
<li>{weather}</li>
<li>姓名:{name}</li>
<li>年龄:{age + 1}</li>
<li onClick={speak}>性别:{sex}</li>
</ul>
</div>
)
}
}
//给MyComponent的prop增加类型和必填
MyComponent.propTypes = {
//类型为string,必传
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number.isRequired,
//传方法
speak:PropTypes.func.isRequired,
}
//给MyComponent的prop添加默认值,如果不传性别是男
MyComponent.defaultProps={
sex:'男'
}
function speak(){
console.log('说话');
}
const p = { name: '李华', age: 20, speak:function(){ console.log('唱歌');} }
ReactDOM.render(<MyComponent name='小明' age={18} speak={speak} />, document.getElementById('root'))
ReactDOM.render(<MyComponent {...p} />, document.getElementById('root2'))
</script>
</body>
</html>
常用:给props加类型默认值限制(在类里写)
static 加上他之后你在类里定义的属性都不会在类的实例上,而是在他这个类本身加属性。
那上面的类型MyComponent.propTypes就能简写成以下的
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>props的参数限制</title>
</head>
<body>
<div id="root"></div>
<div id="root2"></div>
<script src="../依赖包/旧版本/react.development.js"></script>
<script src="../依赖包/旧版本/react-dom.development.js"></script>
<script src="../依赖包/旧版本/babel.min.js"></script>
<!-- 引入类型包 -->
<script src="../依赖包/旧版本/prop-types.js"></script>
<script type="text/babel">
class MyComponent extends React.Component {
state = {
weather: '晴天',
}
render() {
const { weather } = this.state
const { name, age, sex, speak } = this.props
return (
<div>
<ul>
<li>{weather}</li>
<li>姓名:{name}</li>
<li>年龄:{age + 1}</li>
<li onClick={speak}>性别:{sex}</li>
</ul>
</div>
)
}
//给MyComponent的prop增加类型和必填
static propTypes = {
//类型为string,必传
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number.isRequired,
//传方法
speak: PropTypes.func.isRequired,
}
//给MyComponent的prop添加默认值,如果不传性别是男
static defaultProps = {
sex: '男'
}
}
function speak() {
console.log('说话');
}
const p = { name: '李华', age: 20, speak: function () { console.log('唱歌'); } }
ReactDOM.render(<MyComponent name='小明' age={18} speak={speak} />, document.getElementById('root'))
ReactDOM.render(<MyComponent {...p} />, document.getElementById('root2'))
</script>
</body>
</html>
以上是不写构造器,要是写上构造器,构造器里和super里一定要接props,不然就不能通过this.props拿到值。以下打印undefined
函数式组件使用props
3.refs(用来获取dom节点,类似与vue ref)
1.字符串类型的ref(即将废弃,不推荐)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>字符串ref</title>
</head>
<body>
<div id="root"></div>
<div id="root2"></div>
<script src="../依赖包/旧版本/react.development.js"></script>
<script src="../依赖包/旧版本/react-dom.development.js"></script>
<script src="../依赖包/旧版本/babel.min.js"></script>
<script src="../依赖包/旧版本/prop-types.js"></script>
<script type="text/babel">
class Demo extends React.Component {
btn=()=>{
const {input1}=this.refs
alert(`${input1.value}`)
}
btn2=()=>{
const {input2}=this.refs
alert(`${input2.value}`)
}
render() {
return (
<div>
<input type="text" ref='input1'/>
<button onClick={this.btn}>点击获取左边的input值</button>
<input type="text" onBlur={this.btn2} ref='input2'/>
</div>
)
}
}
ReactDOM.render(<Demo/>, document.getElementById('root'))
</script>
</body>
</html>
2.回调形式的ref(在组件实例自身上放input1和input2)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>字符串ref</title>
</head>
<body>
<div id="root"></div>
<div id="root2"></div>
<script src="../依赖包/旧版本/react.development.js"></script>
<script src="../依赖包/旧版本/react-dom.development.js"></script>
<script src="../依赖包/旧版本/babel.min.js"></script>
<script src="../依赖包/旧版本/prop-types.js"></script>
<script type="text/babel">
class Demo extends React.Component {
btn=()=>{
const {input1}=this
alert(`${input1.value}`)
}
btn2=()=>{
const {input2}=this
alert(`${input2.value}`)
}
render() {
return (
<div>
<input type="text" ref={(c)=>{this.input=c}}/>
<button onClick={this.btn}>点击获取左边的input值</button>
简写形式
<input type="text" onBlur={c=>this.input=c} ref='input2'/>
</div>
)
}
}
ReactDOM.render(<Demo/>, document.getElementById('root'))
</script>
</body>
</html>
回调形式的ref执行了几次?
如果回调形式的ref是以上面的行内(内联)方式定义的,他在(render调用第二次的时候)更新过程中会被执行两次,第一次是null,第二次返回参数的dom元素
3.class类型的ref
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>字符串ref</title>
</head>
<body>
<div id="root"></div>
<div id="root2"></div>
<script src="../依赖包/旧版本/react.development.js"></script>
<script src="../依赖包/旧版本/react-dom.development.js"></script>
<script src="../依赖包/旧版本/babel.min.js"></script>
<script src="../依赖包/旧版本/prop-types.js"></script>
<script type="text/babel">
class Demo extends React.Component {
btn=()=>{
alert(`${this.input3.value}`)
}
btn2=()=>{
const {input2}=this
alert(`${input2.value}`)
}
//class
saveInput=(c)=>{
this.input3=c
}
render() {
return (
<div>
<input type="text" ref={this.saveInput}/>
{ /* <input type="text" ref={(c)=>{this.input=c}}/> */}
<button onClick={this.btn}>点击获取左边的input值</button>
简写形式
<input type="text" onBlur={c=>this.input=c} ref='input2'/>
</div>
)
}
}
ReactDOM.render(<Demo/>, document.getElementById('root'))
</script>
</body>
</html>
4.React.createRef创建ref(官方最推荐)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>字符串ref</title>
</head>
<body>
<div id="root"></div>
<div id="root2"></div>
<script src="../依赖包/旧版本/react.development.js"></script>
<script src="../依赖包/旧版本/react-dom.development.js"></script>
<script src="../依赖包/旧版本/babel.min.js"></script>
<script src="../依赖包/旧版本/prop-types.js"></script>
<script type="text/babel">
class Demo extends React.Component {
myRef=React.createRef()
myLeftRef=React.createRef()
btn=()=>{
console.log(this.myRef,55);
alert(`${this.myRef.current.value}`)
}
leftBtn=()=>{
alert(this.myLeftRef.current.value);
}
//class
render() {
return (
<div>
<input type="text" ref={this.myLeftRef}/>
{ /* <input type="text" ref={(c)=>{this.input=c}}/> */}
<button onClick={this.leftBtn}>点击获取左边的input值</button>
简写形式
<input type="text" onBlur={this.btn} ref={this.myRef}/>
</div>
)
}
}
ReactDOM.render(<Demo/>, document.getElementById('root'))
</script>
</body>
</html>
React的事件处理(为什么onClick的C要大写)
(1).通过onXxxx指定的事件处理函数
react使用的是自定义(合成)事件,而不是dom的原生事件-----为了更好的兼容性
react中的事件是通过事件委托方式处理的(委托给最外层的元素)------为了高效
(2).通过event.target得到发生事件的dom元素对象。----不要过渡使用ref
受控组件和非受控组件
非受控
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>非受控组件</title>
</head>
<body>
<div id="root"></div>
<div id="root2"></div>
<script src="../依赖包/旧版本/react.development.js"></script>
<script src="../依赖包/旧版本/react-dom.development.js"></script>
<script src="../依赖包/旧版本/babel.min.js"></script>
<script src="../依赖包/旧版本/prop-types.js"></script>
<script type="text/babel">
class LoginFrom extends React.Component {
saveSubmit=(e)=>{
e.preventDefault();
console.log(e);
alert(`用户名是${this.username.value},密码是${this.password.value}`)
}
render() {
return (
<div>
<form onSubmit={this.saveSubmit}>
用户名:<input type="text" name='username' ref={(c)=>{this.username=c}} /> <br />
密码: <input type='password' name='password' ref={c=>this.password=c} /> <br />
<button>登录</button>
</form>
</div>
)
}
}
ReactDOM.render(<LoginFrom />, document.getElementById('root'))
</script>
</body>
</html>
受控
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>受控组件</title>
</head>
<body>
<div id="root"></div>
<div id="root2"></div>
<script src="../依赖包/旧版本/react.development.js"></script>
<script src="../依赖包/旧版本/react-dom.development.js"></script>
<script src="../依赖包/旧版本/babel.min.js"></script>
<script src="../依赖包/旧版本/prop-types.js"></script>
<script type="text/babel">
class LoginFrom extends React.Component {
state={
usernameValue:'',
passwordValue:''
}
saveSubmit=(e)=>{
const {usernameValue,passwordValue}=this.state
e.preventDefault();
alert(`用户名是${usernameValue},密码是${passwordValue}`)
}
username=(e)=>{
const {usernameValue}=this.state
this.setState({
usernameValue:e.target.value
})
}
password=(e)=>{
const {passwordValue}=this.state
this.setState({
passwordValue:e.target.value
})
}
render() {
return (
<div>
<form onSubmit={this.saveSubmit}>
用户名:<input type="text" name='username' onChange={this.username} /> <br />
密码: <input type='password' name='password' onChange={this.password} /> <br />
<button>登录</button>
</form>
</div>
)
}
}
ReactDOM.render(<LoginFrom />, document.getElementById('root'))
</script>
</body>
</html>
区别:
受控组件是指输入类的dom,随着你的输入维护到状态里(state)待到用的时候从状态(state)里取,可以理解为vue的双向绑定v-model。
非受控组件是指在需要获取输入类dom输入的内容时,通过(在点击事件时现用现取)获取dom或者ref现用现取的方式拿到输入的值,被称为非受控组件。
高级函数--函数的柯里化
什么是高阶函数?
如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
1.若A函数,接收的参数是一个函数,那么该函数就是高阶函数。
2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数
(定时器,map)
什么是函数柯里化?
通过函数调用继续返回函数的方法,实现多次接收参数最后统一处理的函数编码形式。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>柯里化</title>
</head>
<body>
<div id="root"></div>
<div id="root2"></div>
<script src="../依赖包/旧版本/react.development.js"></script>
<script src="../依赖包/旧版本/react-dom.development.js"></script>
<script src="../依赖包/旧版本/babel.min.js"></script>
<script src="../依赖包/旧版本/prop-types.js"></script>
<script type="text/babel">
class LoginFrom extends React.Component {
state = {
usernameValue: '',
passwordValue: ''
}
saveSubmit = (e) => {
const { usernameValue, passwordValue } = this.state
e.preventDefault();
alert(`用户名是${usernameValue},密码是${passwordValue}`)
}
saveFrom = (dataType) => {
const { usernameValue, passwordValue } = this.state
return (e) => {
this.setState({
[dataType]: e.target.value
})
}
}
render() {
return (
<div>
<form onSubmit={this.saveSubmit}>
用户名:<input type="text" name='username' onChange={this.saveFrom('usernameValue')} /> <br />
密码: <input type='password' name='password' onChange={this.saveFrom('passwordValue')} /> <br />
<button>登录</button>
</form>
</div>
)
}
}
ReactDOM.render(<LoginFrom />, document.getElementById('root'))
</script>
</body>
</html>
柯里化
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>函数柯里化</title>
</head>
<body>
<script>
function num(a) {
return (b) => {
return (c) => {
return a + b + c
}
}
}
console.log(num(3)(4)(5));
</script>
</body>
</html>
不用函数柯里化实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>不用函数柯里化</title>
</head>
<body>
<div id="root"></div>
<div id="root2"></div>
<script src="../依赖包/旧版本/react.development.js"></script>
<script src="../依赖包/旧版本/react-dom.development.js"></script>
<script src="../依赖包/旧版本/babel.min.js"></script>
<script src="../依赖包/旧版本/prop-types.js"></script>
<script type="text/babel">
class LoginFrom extends React.Component {
state = {
usernameValue: '',
passwordValue: ''
}
saveSubmit = (e) => {
const { usernameValue, passwordValue } = this.state
e.preventDefault();
alert(`用户名是${usernameValue},密码是${passwordValue}`)
}
saveFrom = (dataType, e) => {
const { usernameValue, passwordValue } = this.state
this.setState({
[dataType]: e.target.value
})
}
render() {
return (
<div>
<form onSubmit={this.saveSubmit}>
用户名:<input type="text" name='username' onChange={(e) => { this.saveFrom('usernameValue', e) }} /> <br />
密码: <input type='password' name='password' onChange={e => this.saveFrom('passwordValue', e)} /> <br />
<button>登录</button>
</form>
</div>
)
}
}
ReactDOM.render(<LoginFrom />, document.getElementById('root'))
</script>
</body>
</html>
生命周期
(旧版本)
红色的是挂载,蓝色的是更新,下面蓝色的是将要销毁
挂载
componentWillMount (组件将要挂载)
componentDidMount (组件挂载完毕)(常用,开启定时器,发起网络请求,消息订阅)
componentWillUnmount (组件将要卸载)(常用,关闭定时器,取消消息订阅)
更新
先从setState那条线开始
//控制组件更新的"阀门" 为true允许更新代码继续往下走,为false后面就不会执行了
shouldComponentUpdate(){
return true
}
componentWillUpdate 组件将要更新的钩子
componentDidUpdate 组件更新完毕
再从forceUpdate那条线
最后从父组件传参componentWillReceiveProps开始走
componentWillReceiveProps:传的值下一次更新时触发,第一次不触发
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>生命周期</title>
</head>
<body>
<div id="root"></div>
<script src="../依赖包/旧版本/react.development.js"></script>
<script src="../依赖包/旧版本/react-dom.development.js"></script>
<script src="../依赖包/旧版本/babel.min.js"></script>
<script src="../依赖包/旧版本/prop-types.js"></script>
<script type="text/babel">
class A extends React.Component {
state = {
car: '奔驰'
}
changeCar = () => {
let { car } = this.state
this.setState({
car: '奥迪'
})
}
render() {
const { car } = this.state
return (
<div>
<div>A组件----{car}</div>
<button onClick={this.changeCar}>更改车名字</button>
<B car={car} />
</div>
)
}
}
class B extends React.Component {
componentWillReceiveProps() {
console.log('B-----componentWillReceiveProps');
}
render() {
console.log(this.props);
let { car } = this.props
return (
<div>{car}</div>
)
}
}
ReactDOM.render(<A />, document.getElementById('root'))
</script>
</body>
</html>
(新版本)
废弃了三个钩子
componentWillMount (组件将要挂载)使用需要(UNSAFE_componentWillMount)
componentWillReceiveProps:传的值下一次更新时触发,第一次不触发。 使用需要(UNSAFE_componentWillReceiveProps)
componentWillUpdate 组件将要更新的钩子。使用需要(UNSAFE_componentWillUpdate)
先从挂载开始
对比多了个getDerivedStateFromProps,他返回一个对象
static getDerivedStateFromProps(props, state){
console.log('getDerivedStateFromProps',props, state);
//return {opacity:10}
return state
}
如果返回的对象的key等于state里的key,那state里的那个key就永远没法更新了,并且能接收两个参数第一个参数props,组件传过来的值,第二个参数state,组件里的状态(state)。(用处如果你希望你组件里的状态无论在什么时候(初始化,或者修改)都取决于props,不会被改变了就用。)
getSnapshotBeforeUpdate(会把返回值当参数传给componentDidUpdate)
componentDidUpdate有三个参数('上一次的props','上一次的state','getSnapshotBeforeUpdate的返回值')
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>生命周期</title>
<style>
.item {
height: 30px;
width: 150px;
background: yellow;
}
.arr {
height: 210px;
overflow: auto;
}
</style>
</head>
<body>
<div id="root"></div>
<script src="../依赖包/新版本/react.development.js"></script>
<script src="../依赖包/新版本/react-dom.development.js"></script>
<script src="../依赖包/新版本/babel.min.js"></script>
<script src="../依赖包/新版本/prop-types.js"></script>
<script type="text/babel">
class Home extends React.Component {
state = {
count: 1
}
add=()=>{
let { count } = this.state
this.setState({
count:count+1
})
}
getSnapshotBeforeUpdate(state,props){
return null
}
componentDidUpdate(preProps,preState,height){
console.log(preProps,preState,height,999);
}
componentDidMount() {
console.log('componentDidMount');
}
render() {
let { count } = this.state
return (
<div>
<button onClick={this.add}>点击加一</button> {count}
</div>
)
}
}
ReactDOM.render(<Home opacity={30} count={21} />, document.getElementById('root'))
</script>
</body>
</html>
实战
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>生命周期</title>
<style>
.item {
height: 30px;
width: 150px;
background: yellow;
}
.arr {
height: 210px;
overflow: auto;
}
</style>
</head>
<body>
<div id="root"></div>
<script src="../依赖包/新版本/react.development.js"></script>
<script src="../依赖包/新版本/react-dom.development.js"></script>
<script src="../依赖包/新版本/babel.min.js"></script>
<script src="../依赖包/新版本/prop-types.js"></script>
<script type="text/babel">
class Home extends React.Component {
state = {
homeList: []
}
getSnapshotBeforeUpdate(state,props){
return this.refs.list.scrollHeight
}
componentDidUpdate(preProps,preState,height){
this.refs.list.scrollTop+=this.refs.list.scrollHeight-height
}
componentDidMount() {
setInterval(() => {
let { homeList } = this.state
let news = '作业' + (homeList.length + 1)
this.setState({
homeList: [news, ...homeList]
})
}, 500);
console.log('componentDidMount');
}
render() {
let { homeList } = this.state
return (
<div>
<ul className='arr' ref='list'>
{
homeList.map((item, index) => {
return <li className='item' key={index}>{item}</li>
})
}
</ul>
</div>
)
}
}
ReactDOM.render(<Home opacity={30} count={21} />, document.getElementById('root'))
</script>
</body>
</html>
脚手架
1.全局安装 npm i create-react-app -g
2.找到需要创建的目录执行 create-react-app 文件名
3.public/index.html详解
4.当引入的两个组件类名一样时,会出现后者覆盖前者的问题
解决:
消息订阅与发布
1.npm install pubsub-js
2.在需要用到的地方引入
import PubSub from 'pubsub-js'
3.发布数据
PubSub.publish("data", { data: [], isLoding: true, isSearch: false });
4.接收
componentDidMount(){
this.token= PubSub.subscribe('data',(msg,data)=>{
this.setState({
dataArr:data.data,
isSearch:data.isSearch,
isLoding:data.isLoding,
err:data.err,
})
})
}
卸载
componentWillUnmount(){
PubSub.unsubscribe(this.token)
}
fetch
fetch(`/api1/search/users2?q=${this.searchDom.value}`).then((response) => {
// console.log(response);
return response.json()
},error=>{
// console.log(error);
}).then(res=>{
console.log(res);
},error=>{
console.log(error);
})
优化
fetch(`/api1/search/users2?q=${this.searchDom.value}`).then((response) => {
// console.log(response);
return response.json()
}).then(res=>{
console.log(res);
}).catch(err=>{
console.log(err);
})
在优化
btn = async () => {
PubSub.publish("data", { data: [], isLoding: true, isSearch: false });
try {
const response = await fetch(
`/api1/search/users2?q=${this.searchDom.value}`
);
const data = await response.json();
PubSub.publish("data", {
data: data.items,
isSearch: false,
isLoding: false,
});
console.log(data);
} catch (error) {
PubSub.publish("data", {
data: [],
isLoding: false,
isSearch: false,
err: error.message,
});
console.log(error);
}
路由(route)
1.安装 npm install react-router-dom@5
2021年11月升级到6版本 npm install react-router-dom@6
以下是5版本
声明式
Link:导航区-路由的跳转链接类似于html的a链接,标签里有个to属性用来执行跳转到哪个路由。
Route:展示区-注册路由,标签里有两个属性,path:路径,根据路径匹配,如果匹配上了展示组件,component:代表跳到哪个路由组件里
import React, { Component } from 'react'
import './App.css'
import Home from './components/home'
import About from './components/about'
import { Link, Route } from 'react-router-dom'
export default class App extends Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header"><h2>React Router Demo</h2></div>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 在React中靠路由链接实现切换组件---编写路由链接 */}
<Link className='list-group-item' to={'/about'}>About</Link>
<Link className='list-group-item' to={'/home'}>Home</Link>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
{/* 注册路由 */}
<Route path='/about' component={About} />
<Route path='/home' component={Home} />
</div>
</div>
</div>
</div>
)
}
}
BrowserRouter: 路由类型,指定路由为哪种类型,因为使用路由必须要指定路由类型,这里包在index.js里是因为所有路由都来自于这个app组件一劳永逸,BrowserRouter:地址栏不带#
HashRouter:路由类型,带#,后面跟着的是hash值,特点:#号后面的东西都不会做为资源发送给服务器
细节问题:你写的link被解析成a标签用,把里面的to换成了herf实现监听对路由的跳转
一般组件和路由组件的区别
一般组件:引入后直接调用,不需要使用Route 里面的component属性,如果不传递参数props为空对象,并且传什么,props里就有什么,存放位置也不同,放在components文件夹里
路由组件:引入后需要结合<Route component>使用,存放位置也不同,放在pages文件夹里,默认的props里有三个值history,location,match。
NavLink:控制按钮高亮,标签属性activeClassName,默认是active
import React, { Component } from 'react'
import './App.css'
import Home from './components/home'
import About from './components/about'
import { NavLink, Route } from 'react-router-dom'
export default class App extends Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header"><h2>React Router Demo</h2></div>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 在React中靠路由链接实现切换组件---编写路由链接 */}
<NavLink activeClassName='a' className='list-group-item' to={'/about'}>About</NavLink>
<NavLink activeClassName='a' className='list-group-item' to={'/home'}>Home</NavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
{/* 注册路由 */}
<Route path='/about' component={About} />
<Route path='/home' component={Home} />
</div>
</div>
</div>
</div>
)
}
}
封装NavLink
import React, { Component } from 'react'
import './App.css'
import Home from './pages/home'
import About from './pages/about'
import { Route } from 'react-router-dom'
import MyNavLink from './components/MyNavLink/index'
export default class App extends Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header"><h2>React Router Demo</h2></div>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 在React中靠路由链接实现切换组件---编写路由链接 */}
<MyNavLink to={'/about'}>About</MyNavLink>
<MyNavLink to={'/home'}>Home</MyNavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
{/* 注册路由 */}
<Route path='/about' component={About} />
<Route path='/home' component={Home} />
</div>
</div>
</div>
</div>
)
}
}
MyNavLink(标签内容会通过this.props.children)拿到并放在标签内容里
import React, { Component } from "react";
import { NavLink } from 'react-router-dom'
export default class MyNavLink extends Component {
render() {
return (
<NavLink activeClassName='a' className="list-group-item" {...this.props} />
);
}
}
Switch(通常情况下,path和component是一一对应的关系,Switch可以提高路由匹配的效率(单一匹配))
解决index.html里面样式丢失
exact:开启路由的严格匹配
replace:开启路由的replace模式不留下痕迹,默认不写是push
Redirect:重定向 (一般写在路由最下方,当所有路由都无法匹配时,跳转的到Redirect指定的路由)
<Switch>
<Route path='/about' component={About} />
<Route path='/home' component={Home} />
<Redirect to='/about' />
</Switch>Ï
嵌套路由(二级,多级路由)
import React, { Component } from "react";
import News from "./components/news";
import Message from "./components/message";
import { Switch, Route,Redirect } from "react-router-dom";
import MyNavLink from "../../components/MyNavLink";
export default class Home extends Component {
render() {
return (
<div className="panel-body">
<h3>我是Home的内容</h3>
<div>
<ul className="nav nav-tabs">
<li>
<MyNavLink to="/home/news">
News
</MyNavLink>
</li>
<li>
<MyNavLink to="/home/message">
Message
</MyNavLink>
</li>
</ul>
<Switch>
<Route path="/home/news" component={News}></Route>
<Route path="/home/message" component={Message}></Route>
<Redirect to='/home/news'></Redirect>
</Switch>
</div>
</div>
);
}
}
注册子路由时要写上父路由的path值,路由的匹配是按照注册路由的顺序进行的
路由参数传递
params
向路由组件传递params参数
<Link to={`/home/message/content/${item.title}/${item.id}/${item.content}`}>
{item.title}
</Link>
声明接收params参数
<Switch>
<Route
path="/home/message/content/:title/:id/:content"
component={Content}
></Route>
</Switch>
接收
const {id,title,content}=this.props.match.params
例子:
import React, { Component } from "react";
import Content from "./component/content";
import { Link, Switch, Route } from "react-router-dom";
export default class message extends Component {
state = {
messageList: [
{
title: "消息1",
id: 1,
content: "战胜新冠",
},
{
title: "消息2",
id: 2,
content: "新冠必败",
},
{
title: "消息3",
id: 3,
content: "傻逼新冠",
},
],
};
render() {
const { messageList } = this.state;
return (
<div>
<div>
<ul>
{messageList.map((item) => {
return (
<li key={item.id}>
{/* 向路由组件传递params参数 */}
<Link
to={`/home/message/content/${item.title}/${item.id}/${item.content}`}
>
{item.title}
</Link>
</li>
);
})}
</ul>
</div>
<hr></hr>
<Switch>
{/* 声明接收params参数 */}
<Route
path="/home/message/content/:title/:id/:content"
component={Content}
></Route>
</Switch>
</div>
);
}
}
接收:
import React, { Component } from "react";
export default class content extends Component {
render() {
const {id,title,content}=this.props.match.params
return (
<div className="content">
<ul>
<li>id:{id}</li>
<li>title:{title}</li>
<li>content:{content}</li>
</ul>
</div>
);
}
}
地址栏样式:http://localhost:3000/home/message/content/消息2/2
search(query)
向路由组件传递search参数
<Link to={`/home/message/content/?id=${item.id}&title=${item.title}`}>
{item.title}
</Link>
search参数无需声明接受
<Switch>
{/* search参数无需声明接受 */}
<Route path="/home/message/content" component={Content}></Route>
</Switch>
search接受参数
this.props.location.search
//search: "?id=1&title=%E6%B6%88%E6%81%AF1"
需要引入一个插件把他转成对象
//安装 npm i qs
//import qs from "qs";
const { search } = this.props.location;
const result = qs.parse(search.slice(1));
地址栏样式:http://localhost:3000/home/message/content/?id=1&title=消息1
state
向路由组件传递state参数
<Link to={{pathname:'/home/message/content',state:{id:item.id,title:item.title}}}>
{item.title}
</Link>
state参数无需声明接受
<Switch>
<Route path="/home/message/content" component={Content}></Route>
</Switch>
state接受参数
const { id, title } = this.props.location.state||{};
地址栏样式:http://localhost:3000/home/message/content
因为没在导航看显示,所以每次点击history就会把他存起了,所以刷新也不会丢,但如果你把历史都清了,this.props.location.state就拿不到值了,所以要加个||{}以免报错。
总结
编程式
params
import React, { Component } from "react";
import Content from "./component/content";
import { Link, Switch, Route } from "react-router-dom";
export default class message extends Component {
state = {
messageList: [
{
title: "消息1",
id: 1,
},
{
title: "消息2",
id: 2,
},
{
title: "消息3",
id: 3,
},
],
};
render() {
const { messageList } = this.state;
return (
<div>
<div>
<ul>
{messageList.map((item) => {
return (
<li key={item.id}>
{/* 向路由组件传递search参数 */}
<Link
to={`/home/message/content/?id=${item.id}&title=${item.title}`}
>
{item.title}
</Link>
<button
onClick={() => {
this.pushShow(item.id, item.title);
}}
>
push
</button>
<button
onClick={() => {
this.replaceShow(item.id, item.title);
}}
>
replace
</button>
</li>
);
})}
</ul>
</div>
<hr></hr>
{/* 需要接受 */}
<Switch>
<Route
path="/home/message/content/:id/:title"
component={Content}
></Route>
</Switch>
</div>
);
}
pushShow = (id, title) => {
this.props.history.push(`/home/message/content/${id}/${title}`);
};
replaceShow = (id, title) => {
this.props.history.replace(`/home/message/content/${id}/${title}`);
};
}
search(修改按钮里面的)
this.props.history.push(`/home/message/content/?id=${id}&title=${title}`);
state
import React, { Component } from "react";
import Content from "./component/content";
import { Link, Switch, Route } from "react-router-dom";
export default class message extends Component {
state = {
messageList: [
{
title: "消息1",
id: 1,
},
{
title: "消息2",
id: 2,
},
{
title: "消息3",
id: 3,
},
],
};
render() {
const { messageList } = this.state;
return (
<div>
<div>
<ul>
{messageList.map((item) => {
return (
<li key={item.id}>
{/* 向路由组件传递search参数 */}
<Link
to={`/home/message/content/?id=${item.id}&title=${item.title}`}
>
{item.title}
</Link>
<button
onClick={() => {
this.pushShow(item.id, item.title);
}}
>
push
</button>
<button
onClick={() => {
this.replaceShow(item.id, item.title);
}}
>
replace
</button>
</li>
);
})}
</ul>
</div>
<hr></hr>
<Switch>
<Route
path="/home/message/content"
component={Content}
></Route>
</Switch>
</div>
);
}
pushShow = (id, title) => {
this.props.history.push(`/home/message/content`,{id:id,title:title});
};
replaceShow = (id, title) => {
this.props.history.replace(`/home/message/content`,{id:id,title:title});
};
}
withRouter(针对一般组件身上没有路由组件身上的api,history这个方法,但又想实现跳转功能,很重要)
import React, { Component } from "react";
import {withRouter} from 'react-router-dom'
class Header extends Component {
render() {
return (
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header">
<h2>React Router Demo</h2>
<button onClick={this.goBack}>前进</button>
<button onClick={this.goHach}>回退</button>
</div>
</div>
);
}
goBack=()=>{
console.log(this.props);
}
goHach=()=>{
}
}
export default withRouter(Header)
HashRouter与BrowserRouter的区别
redux(react的状态管理类似于vue的vuex)
安装 npm install --save redux react-redux
mini版
1.创建一个store.js
//1.引入createStore,用于创建redux中最为核心的store对象
import {legacy_createStore as createStore} from 'redux'
//引入为conut组件服务的reducer
import countReducer from './count_reducer'
//暴露store
export default createStore(countReducer)
2.创建一个count_reducer.js
//1.该文件是用于创建一个为count组件服务的reducer,reducer的本质就是一个函数。
//2.reducer函数会接收两个参数,分别是之前的状态(preState),动作对象(action)
const initState=0
export default function countReducer(preState=initState, action) {
//从action对象中获取:type,data
const { type, data } = action
//根据type决定
switch (type) {
case 'add':
return preState + data
case 'minus':
return preState - data
default:
return preState
}
}
3.页面引入
//用于获取redux中的状态
import store from "../../redux/store";
初始化状态
store.getState()
指定操作类型和操作的值
store.dispatch({type:'add',data:parseInt(this.selectValue.value)})
完整代码
import React, { Component } from "react";
import { Button } from "antd";
//用于获取redux中的状态
import store from "../../redux/store";
export default class ReduxTc extends Component {
state = {
value: 0,
};
render() {
return (
<div>
<div>click {store.getState()} times</div>
<select ref={(e)=>{this.selectValue=e}}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<Button onClick={this.Add}>加</Button>
<Button onClick={this.minus}>减</Button>
<Button onClick={this.odd}>奇数才加</Button>
<Button onClick={this.asyncAdd}>异步加</Button>
</div>
);
}
Add = () => {
store.dispatch({type:'add',data:parseInt(this.selectValue.value)})
};
minus = () => {
store.dispatch({type:'minus',data:parseInt(this.selectValue.value)})
};
odd = () => {
if (store.getState() % 2 !== 0) {
store.dispatch({type:'add',data:parseInt(this.selectValue.value)})
}
};
asyncAdd = () => {
setTimeout(() => {
store.dispatch({type:'add',data:parseInt(this.selectValue.value)})
}, 2000);
};
}
因为redux只负责管理状态不负责刷新render
所以在index.js里利用store.subscribe更新
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter as Router } from 'react-router-dom'
import App from './App';
import store from './redux/store';
import { ConfigProvider } from 'antd';
const root = ReactDOM.createRoot(document.getElementById('root'));
store.subscribe(()=>{
root.render(
<React.StrictMode>
<Router>
<ConfigProvider
theme={{
token: {
colorPrimary: 'red',
},
}}
>
<App />
</ConfigProvider>
</Router>
</React.StrictMode>
);
})
root.render(
<React.StrictMode>
<Router>
<ConfigProvider
theme={{
token: {
colorPrimary: 'red',
},
}}
>
<App />
</ConfigProvider>
</Router>
</React.StrictMode>
);
完整版
增加了action(用来存放操作类型的)
用来避免类型写错
reducer 用来操作和初始化数据的
store 管理状态的仓库
用来刷新render
reduxTc.jsx
import React, { Component } from "react";
import { Button } from "antd";
//用于获取redux中的状态
import store from "../../redux/store";
//引入action
import { createAdd,createMinus } from "../../redux/conut_action";
export default class ReduxTc extends Component {
state = {
value: 0,
};
render() {
return (
<div>
<div>click {store.getState()} times</div>
<select ref={(e)=>{this.selectValue=e}}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<Button onClick={this.Add}>加</Button>
<Button onClick={this.minus}>减</Button>
<Button onClick={this.odd}>奇数才加</Button>
<Button onClick={this.asyncAdd}>异步加</Button>
</div>
);
}
Add = () => {
store.dispatch(createAdd(this.selectValue.value*1))
};
minus = () => {
store.dispatch(createMinus(this.selectValue.value*1))
};
odd = () => {
if (store.getState() % 2 !== 0) {
store.dispatch(createAdd(this.selectValue.value*1))
}
};
asyncAdd = () => {
setTimeout(() => {
store.dispatch(createAdd(this.selectValue.value*1))
}, 2000);
};
}
异步action
需要安装插件
npm install redux-thunk
count_action.js (添加异步action)
conutant.js(操作类型不变)
count_reducer.js(初始化reducer和操作不变)
store.js(添加applyMiddleware用于执行中间件thunk)
redux_tc.jsx
import React, { Component } from "react";
import { Button } from "antd";
//用于获取redux中的状态
import store from "../../redux/store";
//引入action
import { createAdd,createMinus,createAsyncAdd } from "../../redux/conut_action";
export default class ReduxTc extends Component {
state = {
value: 0,
};
render() {
return (
<div>
<div>click {store.getState()} times</div>
<select ref={(e)=>{this.selectValue=e}}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<Button onClick={this.Add}>加</Button>
<Button onClick={this.minus}>减</Button>
<Button onClick={this.odd}>奇数才加</Button>
<Button onClick={this.asyncAdd}>异步加</Button>
</div>
);
}
Add = () => {
store.dispatch(createAdd(this.selectValue.value*1))
};
minus = () => {
store.dispatch(createMinus(this.selectValue.value*1))
};
odd = () => {
if (store.getState() % 2 !== 0) {
store.dispatch(createAdd(this.selectValue.value*1))
}
};
//使用异步action方法就行了
asyncAdd = () => {
// setTimeout(() => {
store.dispatch(createAsyncAdd(this.selectValue.value*1,500))
// }, 2000);
};
}
react-redux
安装 npm install react-redux
redux开发者工具使用
让redux工具亮
1. npm install redux-devtools-extension
2.引入import {composeWithDevTools} from 'redux-devtools-extension'
react拓展知识
this.setState(对象式写法),react在更新状态的时候其实是一个异步的更新,这就导致以下的写法拿不到更新后的值。
所以要传一个回调,在回调里拿更新值
import React, { Component } from 'react'
export default class Demo extends Component {
state={
count:0
}
render() {
const {count}=this.state
return (
<div>
<h1>当前求和为{count}</h1>
<button onClick={this.add}>加</button>
</div>
)
}
add=()=>{
//对象式
const {count}=this.state
this.setState({count:count+1},()=>{
console.log(this.state.count);//捕获每次更新状态的值
})
}
}
函数式写法
import React, { Component } from 'react'
export default class Demo extends Component {
state={
count:0
}
render() {
const {count}=this.state
return (
<div>
<h1>当前求和为{count}</h1>
<button onClick={this.add}>加</button>
</div>
)
}
add=()=>{
//函数式
this.setState((state,props)=>{
return {count:state.count+1}
},()=>{
console.log(this.state.count);
})
}
}
lazyLoad(路由懒加载)
将原来的引入方式注释掉改用const Home=lazy(()=>import('./pages/home'))
从react里引入lazy,Suspense
import React, { Component,lazy,Suspense } from 'react'
import './App.css'
import { Route, Switch, Redirect } from 'react-router-dom'
import MyNavLink from './components/MyNavLink/index'
// import Home from './pages/home'
// import About from './pages/about'
const Home=lazy(()=>import('./pages/home'))
const About=lazy(()=>import('./pages/about'))
export default class App extends Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header"><h2>React Router Demo</h2></div>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 在React中靠路由链接实现切换组件---编写路由链接 */}
<MyNavLink to={'/about'}>About</MyNavLink>
<MyNavLink to={'/home'}>Home</MyNavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
{/* 注册路由 */}
<Suspense fallback={<h1>loding.....</h1>}>
<Switch>
<Route path='/about' component={About} />
<Route path='/home' component={Home} />
<Redirect to='/about' />
</Switch>
</Suspense>
</div>
</div>
</div>
</div>
)
}
}
Hook(让函数式组件可以修改自己状态)
1.const [state,setState]=React.useState(0)
state初始化状态,setState可修改的方法
import React from "react";
export default function Demo() {
const [conut,setCount]=React.useState(0)
const add=()=>{
setCount(conut+1)
}
return (
<div>
<h1>当前求和为{conut}</h1>
<button onClick={add}>加</button>
</div>
);
}
2.React.useEffect
React.useEffect(()=>{
},[])
接受两个参数
不传默认监听所有变化(相当于componentDidMount和componentDidUpdate),放一个空数组相当于componentDidMount,return返回一个函数相当于 componentWillUnmount。
import React from "react";
import ReactDOM from "react-dom";
import {root} from '../../index'
export default function Demo() {
const [conut,setCount]=React.useState(0)
const add=()=>{
setCount(conut+1)
}
const unmount=()=>{
root.unmount()
}
React.useEffect(()=>{
let timer= setInterval(()=>{
console.log('@');
setCount(conut+1)
// setCount(conut=>conut+1)
},1000)
return ()=>{
clearInterval(timer)
console.log('###');
}
},[])
return (
<div>
<h1>当前求和为{conut}</h1>
<button onClick={add}>加</button>
<button onClick={unmount}>卸载</button>
</div>
);
}
RefHook
import React from "react";
import {root} from '../../index'
export default function Demo() {
const myref=React.useRef()
const [conut,setCount]=React.useState(0)
const add=()=>{
console.log(myref.current.value);
setCount(conut+1)
}
const unmount=()=>{
root.unmount()
}
return (
<div>
<h1>当前求和为{conut}</h1>
<input type="text" name="" id="" ref={myref} />
<button onClick={add}>加</button>
<button onClick={unmount}>卸载</button>
</div>
);
}
Fragment(vue3提出的一样减少teplete)
import React,{Fragment} from "react";
import {root} from '../../index'
export default function Demo() {
const myref=React.useRef()
const [conut,setCount]=React.useState(0)
const add=()=>{
console.log(myref.current.value);
setCount(conut+1)
}
const unmount=()=>{
root.unmount()
}
return (
<Fragment>
<h1>当前求和为{conut}</h1>
<input type="text" name="" id="" ref={myref} />
<button onClick={add}>加</button>
<button onClick={unmount}>卸载</button>
</Fragment>
);
}
context(用于祖孙传值)
import React, { Component } from "react";
const MyContext = React.createContext();
const { Provider, Consumer } = MyContext;
export default class A extends Component {
state = {
username: "小明",
age: 18,
};
render() {
const { username, age } = this.state;
return (
<div>
<h1>
我是A组件 {username}=={age}
</h1>
我的儿子是
<Provider value={{ username, age }}>
<B />
</Provider>
</div>
);
}
}
class B extends Component {
//类似组件接受
static contextType = MyContext;
render() {
const { username, age } = this.context;
return (
<div>
<h2>我是B组件{username}</h2>
我的儿子是 <C />
</div>
);
}
}
function C() {
//函数组件接受
return (
<h3>
我是C组件
<Consumer>
{(value) => {
return `${value.username}`;
}}
</Consumer>
</h3>
);
}
优化
由于有时候this.setState更新东西没更新到子组件用到的属性,也会刷新子组件的render,导致性能问题这里把引入的Component换成PureComponent,PureComponent重写了shouldComponentUpdate
import React, { PureComponent } from "react";
const MyContext = React.createContext();
const { Provider, Consumer } = MyContext;
export default class A extends PureComponent {
state = {
username: "小明",
age: 18,
};
render() {
const { username, age } = this.state;
return (
<div>
<h1>
我是A组件 {username}=={age}
</h1>
我的儿子是
<Provider value={{ username, age }}>
<B />
</Provider>
</div>
);
}
}
class B extends PureComponent {
//类似组件接受
static contextType = MyContext;
render() {
const { username, age } = this.context;
return (
<div>
<h2>我是B组件{username}</h2>
我的儿子是 <C />
</div>
);
}
}
function C() {
//函数组件接受
return (
<h3>
我是C组件
<Consumer>
{(value) => {
return `${value.username}`;
}}
</Consumer>
</h3>
);
}
但是PureComponent有弊端
引用地址相同时默认阀门为false
renderProps
import React, { Component, Fragment } from "react";
export default class Demo extends Component {
render() {
return (
<Fragment>
<h1>我是demo</h1>
<hr />
<A render={(name) => <B name={name} />} />
</Fragment>
);
}
}
class A extends Component {
state = {
name: "tom",
};
render() {
const { name } = this.state;
return (
<Fragment>
<h1>我是A组件</h1>
{this.props.render(name)}
</Fragment>
);
}
}
class B extends Component {
render() {
return <Fragment>{this.props.name}</Fragment>;
}
}