-
指令系统
-
MVVM架构 Model(模型–数据) View(视图) ViewModel(模型-视图–桥梁)
vue2核心:Object.defineproperty监听对象中属性的变化
-
computed watch
-
组件化 spa单页面应用
-
虚拟dom
-
路由 (路由嵌套,动态路由,路由守卫)
-
vuex
1-2-React简介
https://react.docschina.org/
-
用于构建用户界面的JavaScript库
-
facebook的内部项目(Instagram) 2013年5月 开源
-
React设计思想及其独特,不是完整的MVC。知识MVC中的v
MVC Model(模型–数据) View(视图) control(控制层)
1-3-前端三大主流框架
- Angular 出现最早
- react 最火
- vue 最流行
1-4-React和vue的对比
- 组件方面
- 模块化 :从代码分析,把一些可复用的代码,抽离为单个模块
- 组件化
- vue如何实现组件化
- vue文件(template,script,style)
- react没有像vue这样的组件模块文件,也是组件化开发,一切都是以js实现
- vue如何实现组件化
1-5-React优点
- 设计思想独特,一切基于js并且实现了组件化开发
- 虚拟dom
- JSX语法(js+xml)
- 跨浏览器兼容
- 单向数据流
1-6-React缺点
不是完整的MVC框架,不适用于复杂的大型项目
1-7-虚拟dom
虚拟dom----javascript对象 (Virtual DOM )
数据发生变化,react就会重新构建整个dom树,使用diff算法比较当前的dom树和上一次的dom树,得到dom结构变化的部分,再根据变化的部分进行实际的dom操作
1-8-JSX语法
- 将html代码直接写在JavaScript代码中,不加任何引号
1-9-React使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- 2:引入模块 -->
<!-- react核心模块 创建组件 虚拟dom元素 生命周期 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<!-- 把创建好的组件 和虚拟dom放到真实的dom上 -->
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
</head>
<body>
<!-- 步骤 1: 添加一个 DOM 容器到 HTML -->
<div id="app"></div>
</body>
<script>
// console.log(ReactDOM)
// console.log(document.getElementById('app'))
/*
1:创建虚拟dom
参数1:元素的名称 字符串
参数2:对象或null 当前dom元素的属性
参数3:子节点 其他的虚拟dom 文本
*/
const a=React.createElement('a',{href:'#'},'百度链接')
const div=React.createElement('div',null,a)
console.log(a)
/*把虚拟那dom渲染到真实的dom上
参数1:要渲染虚拟dom元素
参数2:指定页面上的一个容器
*/
ReactDOM.render(div,document.getElementById('app'))
// ReactDOM.render(div,'#app')
// Target container is not a DOM element. 目标容器不是一个dom元素
</script>
</html>
1-10-JSX使用
-
引入
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
-
使用jsx
<script type="text/babel"> //jsx语法使用 </script> 只有script的type的属性值为type='text/babel' 才能识别jsx语法
-
代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script> <!-- 1:要使用jsx语法 引入babel.js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <style> .box{ width:200px; height:200px; background-color: red; } </style> </head> <body> <div id="app"></div> </body> <!-- 在script便签中使用jsx语法 --> <script type="text/babel"> // 1:创建虚拟dom // let div=<div><a href='#'>百度连接</a></div> // // console.log(div) // ReactDOM.render(div,document.getElementById('app')) // 优雅的写法 ReactDOM.render(( <div className="box"> <a href='#'>百度连接</a> </div> ),document.getElementById('app')) //** Adjacent JSX elements must be wrapped in an enclosing tag (14:4) // 只能有一个根节点 // Invalid DOM property `class`. Did you mean `className`? // in div // React中使用className定义类名 </script> </html>
-
错误集锦
Adjacent JSX elements must be wrapped in an enclosing tag (14:4)
// 只能有一个根节点Invalid DOM property
class
. Did you meanclassName
?in divReact中使用className定义类名
-
jsx语法
jsx语法中,变量/表达式 要写在 {} 中 jsx语法中 遇到 <> 解析为html标签,遇到 {} 解析为js代码 {} 中 可以使用变量,表达式,数组,三目运算(但是不能是一个对象,if语句,for循环)
-
代码
- if语句的书写
- for循环的书写
- 注释的书写
<script type="text/babel"> /* jsx语法中,变量/表达式 要写在 {} 中 jsx语法中 遇到 <> 解析为html标签,遇到 {} 解析为js代码 {} 中 可以使用变量,表达式,数组,三目运算(但是不能是一个对象,if语句,for循环) */ let num=5; const arr=["男生","女生"] const arr1=[<div>123</div>,<div>456</div>,<div>789</div>] const arr2=['<div>123</div>','<div>456</div>','<div>789</div>'] let flag=false const obj={ name:"张三", age:25 } ReactDOM.render(( <div className="box"> <a href='#'>{num}</a> {num+8} {arr} <hr/> {arr1} <hr/> {flag?'中公':"成都中公"} <hr/> {arr2} <hr/> {/*obj.name*/} {/*注释代码*/} {'123'+num} <hr/> { (function(){ if(flag){ return (<div>nihao</div>) }else{ return (<div>大家好</div>) } })() } <hr/> { arr.map((item,index)=>{ return (<li key={index}>{item}</li>) }) } </div> ),document.getElementById('app')) </script>
1-11-样式处理
-
class处理
注意 属性名为className
<标签 className={条件判断?'类名1':'类名2'}></标签>
-
style处理
style的值是一个样式描述对象
<标签 style={ {属性1:'值',属性1:'值' ...} }></标签>
-
错误集锦
Uncaught Error: The
style
prop expects a mapping from style properties to values, not a string. For example, style={{marginRight: spacing + ‘em’}} when using JSX.jsx语法中,标签的style属性值不能是一个字符串需要是一个对象
-
代码
<script type="text/babel"> ReactDOM.render(( <div> <div className={10<20?'box active':'small'}></div> {/*<h2 style="color:red;font-size:60px;">二级标题</h2>*/} <h2 style={{color:'red',fontSize:'60px'}}>二级标题</h2> </div> ),document.getElementById('app')) </script>
2-1脚手架
https://www.html.cn/create-react-app/docs/getting-started/
-
Create React App
-
安装脚手架
cnpm install create-react-app -g
-
检查是否安装成功
create-react-app -V
2-2:初始化项目
-
创建项目
create-react-app 项目名称 //默认使用的yarn下载包 没有就使用npm //下载yarn cnpm install yarn yarn -version //检查是否安装成功
-
进入项目目录
cd 项目名称
-
启动项目
npm start //如果安装了yarn可以执行 yarn start
2-3:目录结构
.git 文件夹 版本管理文件夹 不用管
.gitignore 文件 版本管理时被忽略的文件 不用管
node_modules 文件夹 项目依赖包文件
public 静态文件夹 类似于vue项目目录下的static
index.html 唯一的页面文件
package.json 包说明文件
README.md 说明文件
yarn.lock 使用的是yarn管理工具
src
App.css 根组件样式
App.js 根组件
App.test.js 测试文件 【不用管】
index.css 全局样式文件
index.js 项目入口文件 类似于vue中的main.js
logo.svg
serviceWorker.js 服务测试文件 【不用管,可以删除】
setupTests.js 服务测试文件 【不用管,可以删除】
-
删除掉测试文件后的index.js
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; ReactDOM.render( <App />, document.getElementById('root') );
2-4组件嵌套
-
1:创建About.js组件
import React from 'react' function About(){ return ( <div> about </div> ) } export default About;
-
2:使用组件 app.js
import React from 'react' import About from './components/About' // 所有的import引入都写在最上面 let num=45; // 组件名字首字母一定要大写!!! function App(){ const arr=["apple","orange"] return ( <div> {arr} <hr/> {num} <hr/> 123 <hr/> <About></About> </div> ) } export default App;
2-5组件创建方式(2种)及区别
-
函数式组件(无状态组件)
import React from 'react' // 函数式组件(无状态组件) // 没有this 没有状态state 有属性props 没有生命周期 只能做一些普通的渲染html function About(props){ let num=4; console.log(this,"this") //undefined return ( <div> about </div> ) } export default About;
-
类组件
import React,{Component} from 'react' // 类组件 有this 有生命周期 有属性 可以实现更多的功能 // class Child extends React.Component{ class Child extends Component{ // 生命周期钩子函数 渲染函数 组件被使用 render函数就会执行 render(){ console.log(this,8) //Child组件实例 console.log(this.props,8) return ( <div> child--- </div> ) } } export default Child
day 02
一:回顾
组件创建方式
二:state
react中数据改变,视图上也跟着改变,数据要放在state中
state类似于vue中的data,声明的变量只能在当前组件中使用
一般在构造函数(constructor)中,使用this.state={} 来声明变量
使用this.state.xx读取值
使用this.setState()方法来修改值,并且视图上会检测到数据的变化
-
定义数据
constructor(){ super() this.state={ 变量:值, 变量:值 } }
-
访问数据
this.state.xx
-
修改数据
this.setState({ num:this.state.num+5 })
-
代码
import React,{Component} from 'react' /* react中数据改变,视图上也跟着改变,数据要放在state中 state类似于vue中的data,声明的变量只能在当前组件中使用 一般在构造函数(constructor)中,使用this.state={} 来声明变量 使用this.state.xx读取值 使用this.setState()方法来修改值,并且视图上会检测到数据的变化 */ class Mystate extends Component{ // 实例化类的时候,constructor函数自动执行 constructor(){ super(); //继承父类的构造函数 初始化this // console.log(this) this.state={ //1--声明变量 time:new Date().toLocaleTimeString(), num:25 } } changenum(){ // 修改state中num的值 // this.state.num += 5; // 上述这样无法修改,虽然state中数据变化 但是视图不更新 // console.log(this.state.num,"num") // 要修改state中的数据,视图也要跟着改变 // this.setState({ // num:60 // }) this.setState({ num:this.state.num+5 }) // setState会去修改state中的数据,修改完会调用render函数,重新渲染视图 } render(){ const {num,time}=this.state return ( <div> {/* 访问状态 */} {this.state.num}---{num} <button onClick={this.changenum.bind(this)}>修改num</button> <hr/> 当前时间是:{this.state.time}---{time} </div> ) } } export default Mystate // bind call apply 使用及区别
-
如果我们要修改的值是在原来的基础上变化,建议使用setState里面传入回调函数
this.setState(state=>{ return { //state表示原来的状态数据 属性名:值 } }) //简写 this.setState(state=>({ 属性名:值 }))
- 代码
import React from 'react' class About extends React.Component{ constructor(){ super(); this.state={ arr:[ {id:1,title:"香蕉"}, {id:2,title:"apple"}, {id:3,title:"orange"}, {id:4,title:"pingg"} ], num:25 } } add(){ // {id:5,title:"梨"} // this.state.arr.push({id:5,title:"梨"}) // this.setState({ // arr:this.state.arr // }) // 如果我们要修改的值是在原来的基础上变化,建议使用setState里面传入回调函数 // this.setState(state=>{ //state表示原来的状态数据 // // console.log(state,"state") // state.arr.push({id:5,title:"梨"}) // return { // arr:state.arr // } // }) // 简写 this.setState(state=>({ arr:state.arr.concat([{id:5,title:"梨"}]) })) } render(){ return ( <div> <button onClick={this.add.bind(this)}>添加一条数据</button> <ul> { this.state.arr.map(item=>{ return ( <li key={item.id}>{item.title}</li> ) }) } </ul> </div> ) } } export default About;
-
在setState之后去使用某个state中的数据,用的是之前的值,我们需要使用变化之后的值
在setState的第二个参数回调函数里面去执行操作
this.setState({ 属性名:值 },()=>{ console.log(this.state.属性名) //这里获取的state的值是修改后的新值 }) console.log(this.state.属性名) //旧值 setState是异步操作
-
代码
import React,{Component} from 'react' class Child extends Component{ constructor(){ super() this.state={ num:1 } } add(){ /** * 在setState之后去使用某个state中的数据,用的是之前的值,我们需要使用变化之后的值 * 在setState的第二个参数回调函数里面去执行操作 * * */ let newNum=Math.ceil(Math.random()*100) this.setState({ num:newNum },()=>{ //异步执行完执行这里的回调 console.log(this.state.num,2) }) console.log(this.state.num,1) //获取的是旧值 // 因为setState是一个异步操作 } render(){ return ( <div> <h2>{this.state.num}</h2> <button onClick={this.add.bind(this)}>修改值</button> </div> ) } } export default Child
三:事件绑定
1:事件绑定
-
传统的事件绑定,事件函数内部的this指向的是window,严格模式下指向的是undefined
-
方式1:[this不能使用]
class 类名 extends React.Component{ 事件函数(){ console.log(this) //undefined } render(){ rerurn (<button on事件名={this.事件函数}></button>) } }
-
方式2:使用bind改变this指向(重点)
class 类名 extends React.Component{ 事件函数(){ console.log(this) //当前类 } render(){ rerurn (<button on事件名={this.事件函数.bind(this)}></button>) } }
-
方式3:箭头函数绑定(重点)
class 类名 extends React.Component{ 事件函数(){ console.log(this) //当前类 } render(){ rerurn (<button on事件名={()=>{this.事件函数()}}></button>) } }
-
2:事件传参
-
bind绑定传参
class 类名 extends React.Component{ 事件函数(形参){ console.log(this) //当前类 } render(){ rerurn (<button on事件名={this.事件函数.bind(this,实参)}></button>) } }
-
箭头函数传参
class 类名 extends React.Component{ 事件函数(形参){ console.log(this) //当前类 } render(){ rerurn (<button on事件名={()=>{this.事件函数(实参)}}></button>) } }
-
代码
import React,{Component} from 'react' class Mystate extends Component{ constructor(){ super(); this.state={ num:5, arr:["香蕉","句子","苹果"] } } fn(){ console.log("fn",this); //undefined console.log(this.state.num) } fn1(){ console.log('fn1',this) console.log(this.state.num) } del(ind){//删除 console.log(ind,"ind") } render(){ return ( <div> <button onClick={this.fn.bind(this)}>es5事件绑定</button> <button onClick={()=>{this.fn1()}}>es6事件绑定</button> <ul> {this.state.arr.map((item,index)=>{ return (<li key={index}> {item} --<button onClick={this.del.bind(this,index)}>删除1</button> --<button onClick={()=>{this.del(index)}}>删除2</button> </li>) })} </ul> </div> ) } } export default Mystate
3:获取事件对象
-
方式1:bind绑定
class 类名 extends React.Component{ 事件函数(e){ console.log(e.target.value) //target 目标对象 } render(){ rerurn (<button on事件名={this.事件函数.bind(this)}></button>) } }
-
方式2:箭头函数
class 类名 extends React.Component{ 事件函数(e){ console.log(e.target.value) //target 目标对象 } render(){ rerurn (<button on事件名={(e)=>{this.事件函数(e)}}></button>) } } //标签点击之后其实执行的是外层的箭头函数,箭头函数里面调用了对应的事件函数 //外层的箭头函数才是标签真正的事件函数 //所以事件对象其实是在箭头函数的形参位置
-
代码
import React from 'react' class About extends React.Component{ constructor(){ super(); } fn(e){ console.log(e.target.value,"fn") } fn1(e){ console.log(e.target.value,"fn1") } render(){ return ( <div> <input type="text" onBlur={this.fn.bind(this)} /> <hr/> <input type="text" onBlur={(e)=>{this.fn1(e)}} /> {/* 标签点击之后其实执行的是外层的箭头函数,箭头函数里面调用了对应的事件函数 外层的箭头函数才是标签真正的事件函数 所以事件对象其实是在箭头函数的形参位置 */} </div> ) } } export default About;
4:既传参又有事件对象
-
方式1:bind绑定
class 类名 extends React.Component{ 事件函数(形参,e){ console.log(e.target.value) //target 目标对象 } render(){ rerurn (<button on事件名={this.事件函数.bind(this,实参)}></button>) } }
-
方式2:箭头函数(重点)
class 类名 extends React.Component{ 事件函数(形参,e){ console.log(e.target.value) //target 目标对象 } render(){ rerurn (<button on事件名={(e)=>{this.事件函数(实参,e)}}></button>) } }
-
代码
import React,{Component} from 'react' class Child extends Component{ constructor(){ super() } fn(val,e){ //es6 console.log(val,"fn") console.log(e.target,"fn") } fn1(val,e){ //es5 console.log(val,"fn1") console.log(e) } render(){ return ( <div> child <button onClick={(e)=>{this.fn("es6传参",e)}}>绑定1-es6</button> <button onClick={this.fn1.bind(this,"es5传参")}>绑定1-es5</button> </div> ) } } export default Child
四:模拟数据双向绑定
- react中表单是单向数据绑定的,react是单向数据流
import React,{Component} from 'react'
class Mystate extends Component{
constructor(){
super();
this.state={
username:"张三",
pass:"",
sex:"女",
des:"描述",
hobby:['吃'],
city:"北京"
}
}
changeUsername(e){
let nVal=e.target.value;
// console.log(nVal)
this.setState({
username:nVal
})
}
changePass(e){
let nVal=e.target.value;
this.setState({
pass:nVal
})
}
changeFields(attr,e){
let nVal=e.target.value;
this.setState({
[attr]:nVal //表达式作为对象的属性 要加[] 对象的属性是字符串类型
})
}
checkboxChange(e){
//判断value值在数组中 有就删除 没有就添加
let nVal=e.target.value;
console.log(1)
let index=this.state.hobby.indexOf(nVal);
if(index==-1){
this.state.hobby.push(nVal)
}else{
this.state.hobby.splice(index,1)
}
this.setState({
hobby:this.state.hobby
})
}
selectChange(e){
// console.log(e.target.value)
// this.setState({
// city:e.target.value
// })
// selectedOptions表示选中的下拉框 是一个数组
console.log(e.target.selectedOptions[0].value)
this.setState({
city:e.target.selectedOptions[0].value
})
}
render(){
return (
<div>
{/*
1:在state中定义一个变量并设置默认值
2:给文本框的value属性设置 state中定义的变量
3:给文本框添加onChange事件---对应一个函数(文本框内容改变执行函数)
4:函数中 获取文本框改变的内容---重新设置state中变量的值
*/}
<p>
用户名:<input type="text" value={this.state.username} onChange={this.changeFields.bind(this,'username')} />
{this.state.username}
</p>
<p>
密码:<input type="password" value={this.state.pass} onChange={this.changeFields.bind(this,'pass')} />
{this.state.pass}
</p>
<p>
性别:
<input type="radio" value="男" onChange={this.changeFields.bind(this,"sex")} checked={this.state.sex=="男"}/>男
<input type="radio" value="女" onChange={this.changeFields.bind(this,"sex")} checked={this.state.sex=="女"}/>女
-----{this.state.sex}
{/*
checked 属性 值为true
onChange事件---获取到value值,更改state中sex的值
*/}
</p>
<p>
留言:
<textarea value={this.state.des} onChange={this.changeFields.bind(this,'des')}></textarea>
{this.state.des}
</p>
<p>
爱好:
<input type="checkbox" value="吃" onChange={this.checkboxChange.bind(this)} checked={this.state.hobby.includes('吃')} />吃
<input type="checkbox" value="喝" onChange={this.checkboxChange.bind(this)} checked={this.state.hobby.includes('喝')} />喝
<input type="checkbox" value="玩" onChange={this.checkboxChange.bind(this)} checked={this.state.hobby.includes('玩')} />玩
<input type="checkbox" value="乐" onChange={this.checkboxChange.bind(this)} checked={this.state.hobby.includes('乐')} />乐
{/*
checked 判断数据在数组中是否存在 存在返回true
onChange事件 有就删除 没有就添加
*/}
</p>
<ul>
{
this.state.hobby.map((item,index)=>{
return (<li key={index}> {item}</li>)
})
}
</ul>
<p>
城市:
<select value={this.state.city} onChange={this.selectChange.bind(this)}>
<option value="上海">上海</option>
<option value="北京">北京</option>
<option value="四川">四川</option>
</select>
</p>
</div>
)
}
}
export default Mystate
day 03
一:组件通信
1:父向子传值
-
实现 :通过自定义属性,子组件不需要定义props,因为每个组件直接有props属性
props不允许被修改,单向数据流
在组件的构造函数里面如果想使用props里面的数据,需要给构造函数传递props并且给super也传递props
修改state状态机的时候,也可以获取到props的值
-
父组件
<子组件标签 自定义属性名="值"></子组件标签>
-
子组件
//类组件 class About extends React.Component{ constructor(props){ super(props); console.log(this.props,"constructor"); // // 在组件的构造函数里面如果想使用props里面的数据,需要 // 给构造函数传递props并且给super也传递props } render(){ // props不允许被修改 单向数据流 console.log(this.props,"about") return ( <div> </div> ) } } //函数式组件 function Child(props){ // 函数式组件中使用props,要给该函数组件传递props console.log(props,"child") return ( <div> </div> ) }
-
修改state状态机的时候,也可以获取到props的值
// 修改state状态机的时候,也可以获取到props的值 this.setState((state,props)=>{ console.log(state,"state") console.log(props,"props") return { num:state.num+props.msg } })
-
2:子向父传值
-
还是通过props实现,父组件将自己的事件函数传递给子组件,子组件调用传递过来的事件函数
-
实现步骤
- 在父组件定义事件函数
- 通过props将父组件的事件函数传递给子组件(通过bind绑定this)
- 子组件调用传递过来的事件函数this.props.fn(实参) 必须放在箭头函数中
-
父组件
//父组件 import React from 'react' import Mystate from './components/Mystate' class 父组件 extends React.Component{ constructor(){ super() this.state={ 变量:值 } } // 1:在父组件定义函数 父组件的事件函数(形参){ // 接收到子组件传递过来的值 形参 this.setState({ 变量:形参 }) } render(){ return ( <div> {/* 2:将父的事件函数parentfn通过props传递给子组件 */} {this.state.childmsg}--- <Mystate fn={this.父组件的事件函数.bind(this)}></Mystate> {/* 在传递的时候,父组件的事件函数名一定要把this绑定好(父组件),否则this指向其他的 */} </div> ) } } export default App;
-
子组件
//子组件 import React,{Component} from 'react' class 子组件 extends Component{ constructor(){ super(); this.state={ message:"我是成都人" } } render(){ return ( <div> Mystate {/* 无法传参,但是可以调用父组件里的事件函数 */} {/* <button onClick={this.props.fn}>点击</button> */} <hr/> {/*可以调用 可以传参 */} <button onClick={()=>{this.props.fn(this.state.message)}}>点击</button> </div> ) } } export default Mystate
3:非父子传值
-
src目录下面创建bus.js文件
// http://nodejs.cn/api/events.html // events是node中事件触发器模块 const EventEmitter = require('events'); class MyEmitter extends EventEmitter {} const myEmitter = new MyEmitter(); export default myEmitter
-
入口文件index.js里面引入,挂载到Component类的原型上,所有的组件都可以使用
import React,{Component} from 'react' import ReactDOM from 'react-dom' import App from './App' import bus from './bus' Component.prototype.$bus=bus; ReactDOM.render(( <App/> ),document.getElementById('root'))
-
在组件中通过 bus.on 监听(注册)事件 ,bus.emit 触发事件
-
Comb.js
import React from 'react' class Comb extends React.Component{ constructor(){ super(); // 监听事件 console.log(123) this.$bus.on('abc',function(str){ console.log(str) }) } render(){ return (<div> Comb.js </div>) } } export default Comb
-
Coma.js
import React from 'react' class Coma extends React.Component{ fn(){ this.$bus.emit('abc',"成都你好") } render(){ return (<div> Coma.js <button onClick={this.fn.bind(this)}>传值</button> </div>) } } export default Coma
-
Comc.js
import React from 'react' class Comc extends React.Component{ render(){ console.log(456) this.$bus.emit('abc',"hello") return (<div> Comc.js </div>) } } export default Comc
二:生命周期
生命周期流程
-
初始化【重点】
- 设置默认的props参数
-
挂载期
- constructor 初始化构造函数 (设置默认的state数据) 【重点】 执行一次
- componentWillMount 组件将要挂载 执行一次
- render 组件被渲染 【重点】执行多次
- componentDidMount组件挂载完成 【重点】–请求数据 执行一次
-
更新期
- props更新
- componentWillReceiveProps
- shouldComponentUpdate 组件是否需要被更新 该函数返回布尔类型值 true就更新 false就不更新
- componentWillUpdate
- render
- componentDidUpdate
- state更新
- shouldComponentUpdate 组件是否需要被更新 该函数返回布尔类型值 true就更新 false就不更新
- componentWillUpdate
- render
- componentDidUpdate
- props更新
不要在更新的生命周期函数里面去调用setState,因为setState会再次更新,变成死循环
-
销毁期
- componentWillUnmount 组件被卸载
-
代码
父组件
import React from 'react' import Coma from './components/Coma' class App extends React.Component{ constructor(){ super() this.state={ isShow:false, title:"乐山大佛洗脚了" } } up_click(){ this.setState({ isShow:true }) } un_click(){ this.setState({ isShow:false }) } changetitle(){ this.setState({ title:"峨眉山也洗脚了" }) } render(){ return ( <div> <button onClick={this.up_click.bind(this)}>创建组件</button> <button onClick={this.un_click.bind(this)}>卸载组件</button> <button onClick={this.changetitle.bind(this)}>修改title</button> <hr/> { this.state.isShow?<Coma title={this.state.title}></Coma>:"" } </div> ) } } export default App;
子组件
import React from 'react' class Coma extends React.Component{ // 挂载期 constructor(){ super() this.state={ num:10 } console.log("constructor") } changeN(){ this.setState({ num:58 }) } componentWillMount(){ console.log("组件将要挂载----componentWillMount") } render(){ // this.setState({ // num:100 // }) // 切记 不能再这里执行setState 会进入死循环 console.log("组件被渲染-----render") return (<div> {this.props.title}------state数据:---{this.state.num}--- <button onClick={this.changeN.bind(this)}>更新state</button> Coma </div>) } componentDidMount(){ console.log("组件挂载完成-----componentDidMount") } // 更新期 componentWillReceiveProps(newprops){ //只有props的值改变才执行 // [参数接收的是新props] console.log("componentWillReceiveProps",newprops) // 可以获取旧的props吗? console.log("componentWillReceiveProps",this.props) //旧的props } shouldComponentUpdate(newprops,newstate){ // 性能优化的钩子函数 必须return // 是否需要重新渲染页面 console.log("shouldComponentUpdate",newprops,newstate) console.log("shouldComponentUpdate",this.state) //旧的state的值 return true } componentWillUpdate(newprops,newstate){ //组件将要被更新 // this.setState({ // num:45 // }) console.log("componentWillUpdate",newprops,newstate) } // render componentDidUpdate(oldprops,oldstate){ //更新完成 // this.setState({ // num:45 // }) // 切记 不能再这里执行setState 会进入死循环 console.log("componentDidUpdate",oldprops,oldstate) } // 销毁期 componentWillUnmount(){ console.log("componentWillUnmount") } } export default Coma
三:props和state的混合使用
注意顺序问题
父组件
import React from 'react'
import Coma from './components/Coma'
class App extends React.Component{
constructor(){
super()
console.log("1--父constructor")
this.state={
c:10
}
}
// 先执行子组件的componentDidMount 再执行父组件的componentDidMount
componentDidMount(){
console.log("6--父componentDidMount")
this.setState({
c:30
})
}
render(){
console.log("2--父render")
return (
<div>
<Coma c={this.state.c}></Coma>
</div>
)
}
}
export default App;
子组件
import React from 'react'
class Coma extends React.Component{
constructor(){
super()
console.log("3--子constructor")
this.state={
p:1
}
}
componentDidMount(){
console.log("5--子componentDidMount")
// this.setState({
// p:this.state.p+this.props.c
// })
this.setState((state,props)=>{
//等待
//子组件再次 渲染之前执行
console.log(state,"state")
console.log(props,"props")
return {
p:state.p+props.c
}
})
}
render(){
console.log("4--子render")
return (<div>
{this.state.p}
</div>)
}
}
export default Coma
四:react中dom元素获取ref
-
ref作用:用于 获取DOM节点内容
-
react中ref的使用
-
第一种
- 1在组件的构造函数中创建一个ref ,赋给变量btn
- 2组件的标签上加上一个ref属性值为构造函数中定义的那个ref变量
- 3通过this.变量.current 获取
import React from 'react' class App extends React.Component{ constructor(){ super() // 1在组件的构造函数中创建一个ref ,赋给变量btn this.btn=React.createRef() } componentDidMount(){ // 在挂载完成之后才可以使用 // 3.通过this.变量.current 获取 console.log(this.btn.current) } fn(){ console.log(this.btn.current.value) } render(){ return ( <div> {/* 2:关联dom节点 组件的标签上加上一个ref属性值为构造函数中定义的那个ref变量 */} <input type="text" ref={this.btn}/> <button onClick={this.fn.bind(this)}>按钮</button> </div> ) } } export default App;
-
第二种
- 1:在组件的标签上定义ref属性,值为字符串(自定义)
- 2:通过this.refs.属性值 去获取
import React from 'react' class App extends React.Component{ constructor(){ super() } componentDidMount(){ console.log(this.refs.abc) } fn(){ console.log(this.refs.abc) } render(){ return ( <div> <button onClick={this.fn.bind(this)}>按钮</button> <input type="text" value="123" ref="abc" /> </div> ) } } export default App;
-
day 04
一:路由
https://reactrouter.com/web/api/Link
在react中没有所谓的路由规则页面,只需要把路由看做是组件即可
1:下载
cnpm install react-router-dom --save
2:路由模式
-
所有的路由操作都要放到路由模式组件中
-
BrowserRouter 外壳
-
HashRouter 外壳 浏览器地址栏加#
所有的路由相关的内容必须都放到路由模式对应的组件下面,往往会将其作为根标签,所以在App.js中配置
3:Switch
-
默认情况下,路由是从上往下匹配的,只要路由path地址匹配正确(模糊匹配),相关的路由组件就都会渲染,这样是不合理的
-
所以使用Switch组件将所有的路由映射包裹在一起,包裹起来之后,匹配到一个不再继续往下匹配
<Switch>
<Route path="/" exact component={Index}></Route>
<Route path="/coma" component={Coma}></Route>
</Switch>
4:Route 路由映射
-
path 地址
-
component 渲染的组件,(不能是字符串)
-
exact 精确匹配
-
使用
<Route path="/" exact component={组件名}></Route>
5:Link
-
跳转组件 类似于a标签
-
使用
<Link to="地址">内容</Link>
6:NavLink
-
跳转组件
-
使用NavLink,当激活class的时候,会自动加class 值为active
-
NavLink会自动匹配,如果当前页面地址和a的地址匹配上,a就会被添加上class,值为active
-
模糊匹配,需要加exact属性
-
activeClassName指定class的值
-
使用
<NavLink exact to="/" activeClassName="aa">首页</NavLink> <NavLink to="/coma">coma</NavLink>
代码
import React from 'react'
import './App.css'
// 引入组件
import {BrowserRouter,HashRouter,Route,Switch,Link,NavLink} from 'react-router-dom'
import Coma from './components/Coma'
import Index from './components/Index'
class App extends React.Component{
render(){
return (
// 所有的路由操作都写在BrowserRouter中
<BrowserRouter>
{/* <Link to="/">首页</Link>
<Link to="/coma">coma</Link> */}
<NavLink exact to="/" activeClassName="aa">首页</NavLink>
<NavLink to="/coma">coma</NavLink>
{/* Route组件就是映射关系对象
设置映射的路由组 件 path指定地址component指定要渲染的组件 exact精确匹配
Switch指定路由匹配到一个就不再向下匹配
*/}
<Switch>
<Route path="/" exact component={Index}></Route>
<Route path="/coma" component={Coma}></Route>
</Switch>
</BrowserRouter>
)
}
}
export default App;
7:动态路由
/news/5
-
1:App.js中路由组件配置
<Route path="/detail/:id" component={Detail}></Route>
-
2:列表组件实现跳转
<Link to={"/detail/"+item.id} >{item.title}</Link>
-
3:详情页接收
this.props.match.params
8:jquery参数
-
1:App.js中路由组件配置 (直接配置路由)
<Route path="/myxq" component={Myxq}></Route>
-
2:列表组件实现跳转
<Link to={"/myxq?id="+item.id} >{item.title}</Link>
-
3:详情页接收
this.props.location.search //得到的是一个字符串 //将字符串转换为对象 // querystring 将字符串转换为对象 import querystring from 'querystring' querystring.parse(this.props.location.search.substring(1))
9:路由嵌套
-
现在是 首页-- ‘/’ 下有二级路由,我们应该去首页对应的组件里去设置二级路由
-
有二级路由的一级路由映射,不能添加exact属性
-
1:App.js中配置一级路由
//有二级路由的一级路由映射,不能添加exact属性 <Switch> <Route path="/login" component={Login}></Route> <Route path="/register" component={Register}></Route> <Route path="/" component={Index}></Route> </Switch>
-
2:首页下有二级路由,在Index组件中配置二级路由
import React from 'react' import './index.css' import { Switch,Route,Link } from 'react-router-dom' import Mytj from './index/Mytj' import Hot from './index/Hot' import Search from './index/Search' class Index extends React.Component{ render(){ return ( <div> <header> <ul> <li> <Link to="/tj">推荐</Link> </li> <li> <Link to="/hot">热歌榜</Link> </li> <li> <Link to="/search">搜索</Link> </li> </ul> </header> <main> <Switch> <Route path="/tj" component={Mytj}></Route> <Route path="/hot" component={Hot}></Route> <Route path="/search" component={Search}></Route> </Switch> </main> </div> ) } } export default Index;
10:编程式导航
-
路由组件:通过Route渲染出来的组件,具有history属性
this.props.history.push('/login') this.props.history.push({pathname:'/login'}) //replace不能存储到浏览器历史记录中 this.props.history.replace('/hot') // 返回历史记录中的上一页 this.props.history.go(-1)
11:高阶组件
-
高阶组件其实就是一个函数(withRouter),该函数的作用就是将原始组件进行一些扩充,提供一些额外的能力
-
让普通组件具有history属性
import { withRouter } from 'react-router-dom' class Myhead extends Component{ } export default withRouter(Myhead)
-
因为Myhead组件不是路由组件,所以在Myhead组件使用编程式导航不起作用,使用withRouter函数,在Myhead组件中就可以使用编程式导航了
12:重定向
-
使用Redirect组件实现跳转
<Redirect></Redirect>
-
代码
<Route path="*"> <Redirect to="/index"></Redirect> </Route>
13:路由拦截
-
Route组件有一个render事件,值为一个函数,函数返回的就是该路由要渲染的信息!页面的路由和Route的path匹配上之后,render事件就会执行,对应的函数就会被调用
-
代码
//1------render简单使用 class App extends React.Component{ render(){ return ( <BrowserRouter> <Switch> <Route path="/login" render={()=><Login/>}></Route> <Route path="/register" render={()=><Register/>}></Route> <Route path="/index" render={()=><Index/>}></Route> </Switch> </BrowserRouter> ) } }
-
优化后的代码—1
class App extends React.Component{ renderComponent(com){ return com } render(){ return ( <BrowserRouter> <Switch> <Route path="/login" render={this.renderComponent.bind(this,<Login/>)}></Route> <Route path="/register" render={this.renderComponent.bind(this,<Register/>)}></Route> <Route path="/index" render={this.renderComponent.bind(this,<Index/>)}></Route> </Switch> </BrowserRouter> ) } }
-
把所有的路由信息配置在一个常量里面,每个路由映射都是一个对象,里面有相关的数据信息,path地址,component组件,component是渲染的组件,exact是精确匹配等等
-
遍历这个常量,配置上path,exact,把路由映射的组件传到render事件函数里
-
1:src目录下新建js/router.js
import Index from "../../components/Index"; import Login from "../../components/Login"; import Register from "../../components/Register"; const routesdata=[ { path:'/login', component:Login, exact:false, needlogin:false }, { path:'/register', component:Register, exact:false, needlogin:false }, { path:'/index', component:Index, exact:false, // 值为true 要登录后才能进入该路由 needlogin:true } ] export default routesdata;
-
2:App.js中引入,然后使用
import React from 'react' import './App.css' import { BrowserRouter,Switch,Route } from 'react-router-dom'; //1:引入 router数组中存放的所有信息 import router from './assets/js/router' class App extends React.Component{ renderComponent(com){ return com } render(){ return ( <BrowserRouter> <Switch> {/*2:循环遍历*/} { router.map((route,key)=>{ return <Route key={key} path={route.path} render={this.renderComponent.bind(this,<route.component />)}></Route> }) } </Switch> </BrowserRouter> ) } } export default App;
-
-
在render的事件函数里面执行判断 是否要登录 实现路由拦截
import React from 'react'
import './App.css'
import { BrowserRouter,Switch,Route,Redirect } from 'react-router-dom';
import router from './assets/js/router'
class App extends React.Component{
renderComponent(r){
// 判断哪些路由需要登录
if(r.needlogin){
// 判断有没有登录信息
if(localStorage.getItem('userinfo')){
return <r.component/>
}else{
return <Redirect to="/login"/>
}
}else{
return <r.component/>
}
}
render(){
return (
<BrowserRouter>
<Switch>
{
router.map((route,key)=>{
return <Route key={key} path={route.path} render={this.renderComponent.bind(this,route)}></Route>
})
}
{/* 不能给render事件函数传递组件,而是需要把这个路由的所有信息给传递过去
*/}
{/* <Route path="/login" render={this.renderComponent.bind(this,<Login/>)}></Route>
<Route path="/register" render={this.renderComponent.bind(this,<Register/>)}></Route>
<Route path="/index" render={this.renderComponent.bind(this,<Index/>)}></Route> */}
</Switch>
</BrowserRouter>
)
}
}
export default App;
二:轮播图
https://www.npmjs.com/package/react-awesome-swiper
-
下载
npm install react-awesome-swiper --save
-
2:使用
import React from 'react'; import AwesomeSwiper from 'react-awesome-swiper'; //this config is same as idangrous swiper const config = { loop : true, autoplay: { delay: 3000, stopOnLastSlide: false, disableOnInteraction: true, }, // Disable preloading of all images preloadImages: false, // Enable lazy loading lazy: true, speed: 500, navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev', }, pagination: { el: '.swiper-pagination', bulletElement : 'li', hideOnClick :true, clickable :true, }, on: { slideChange: function () { console.log(this.activeIndex); }, }, }; class Example extends React.Component { swiperRef = null goNext = () => {//use `this.swiperRef.swiper` to get the instance of Swiper this.swiperRef.swiper.slideNext(); } render() { return ( <AwesomeSwiper ref={ref => (this.swiperRef = ref)} config={config} className="your-classname"> <div className="swiper-wrapper"> <div className="swiper-slide">slider1</div> <div className="swiper-slide">slider2</div> <div className="swiper-slide">slider3</div> </div> <div className="swiper-button-prev"></div><!--左箭头--> <div className="swiper-button-next"></div><!--右箭头--> <div className="swiper-pagination"></div> </AwesomeSwiper> ) } } export default Example;
三:react中axios的使用
-
下载
cnpm install axios --save
-
把vue项目中的http目录复制到react的src目录下,把请求拦截和响应拦截的内容都注释掉
-
index.JS 全局配置
import $axios from './http' Component.prototype.$axios=$axios
-
组件中使用
this.$axios.get() componentDidMount(){ this.$axios.get('http://jsonplaceholder.typicode.com/posts') .then(res=>{ console.log(res.data,"res") }) }
-
react项目中解决跨域问题
-
package.json
"proxy":"http://localhost:3000"
-
重新启动项目
-
-
react项目更改端口
package.json 找scriptes
"scripts": { "start": "set PORT=3002 && react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" },
day 05
一:flux状态管理
-
主要解决:复杂组件间的数据通信
-
view 视图层
-
Actions 动作 接收视图层发出的信息
-
Dispatcher 信息分发中心是action和store的链接中心
-
store 存放状态,状态一旦改变就会更新视图
-
视图 App.js
import React from 'react';
import actions from './store/actions'
import store from './store/store'
class App extends React.Component{
constructor(){
super()
this.state={
user:"",
userarr:store.arr
}
}
fn(e){
this.setState({
user:e.target.value
})
}
componentDidMount(){
store.updateArr(()=>{
this.setState({
userarr:store.arr
})
})
}
adduser(){
// 触发动作,给actions发出信息(调用action中的方法)
actions.add(this.state.user)
}
del(ind){
// 触发actions
actions.del(ind)
}
render(){
return (
<div>
用户名 <input type="text" value={this.state.user} onChange={(e)=>{this.fn(e)}}/>
<button onClick={()=>{this.adduser()}}>添加</button>
<hr/>
<ul>
{
this.state.userarr.map((item,ind)=>{
return (
<li key={ind}>
{item.user} <button onClick={()=>{this.del(ind)}}>删除</button>
</li>
)
})
}
</ul>
</div>
)
}
}
export default App;
- Actions.js
// 接收视图层发出的信息
import dispatcher from './dispatcher'
export default {
add(user){
// 创建action
var action ={
type:'USERADD', //标识性属性 告诉dispatcher我做的添加操作
data:user
}
// 调用dispatcher中的dispatch方法
dispatcher.dispatch(action)
},
del(index){
var action ={
type:'USERDEL',
index
}
dispatcher.dispatch(action)
}
}
- dispatcher.js
// 信息分发中心 派发器
// cnpm install flux --save
import {Dispatcher} from 'flux'
import actions from './actions';
import store from './store'
// 创建Dispatcher实例
const dispatcher=new Dispatcher();
// 使用dispatcher注册方法
// actions中调用dispatcher.dispatch方法其实就是调用的下面注册的方法
dispatcher.register(action=>{
// console.log(action,"action")
// actions中调用dispatcher.dispatch就是执行这里
// 接收action 根据action中的标识性属性做判断,我该触发store中的什么方法
switch(action.type){
case 'USERADD':
// 触发store中的方法
store.add(action.data)
store.emit('changearr')
break;
case 'USERDEL':
store.del(action.index)
store.emit('changearr')
break;
}
})
export default dispatcher;
- store.js
import {EventEmitter} from 'events'
// 让store具有on和emit方法
// Object.assign 将对象进行合并
const store=Object.assign({},EventEmitter.prototype,{
arr:[{user:"张三"}],
add(data){
this.arr.push({user:data});
console.log(this.arr)
},
updateArr(callback){
this.on('changearr',callback)
},
del(ind){
this.arr.splice(ind,1)
}
}
)
export default store;
二:redux(管理状态)
- reducers 就是一个函数,触发dispatch就是调用reducers 这个函数–对状态的操作都在这里执行
- store 仓库 仅仅是存储状态
- action 动作 type-标识性属性
- view 视图层
- redux代码执行流程
- 点击视图层上的添加按钮,执行事件函数
- 在事件函数中 调用dispatch方法, 传递action
- 调用dispatch方法其实就是调用的reducers函数,reducers函数有两个参数(previousState和action)
- 在reducers函数中根据action中的标识性属性做判断对previousState进行增删改操作 ,返回newState
- 再把newState存储到store中,store仅仅是做存储操作
- redux工作流程
- view在redux中会派发action
- action通过store的dispatch方法会派发给store
- store接收action,连同之前的state,一起传递给reducer
- reducer返回新的数据给state
- store去改变自己的state
使用
-
下载
npm install redux --save
-
store的创建
// 1:引入 import {createStore} from 'redux' // 初始化状态 const defaultState={ arr:[] } // reducers就是一个函数 const reducers=function(state=defaultState,action){ const {type,data}=action switch(type){ case 'USER_ADD': state.arr.push({username:data}) console.log(state,"data") break; } return state } // 2:创建store实例 store是action和reducers的桥梁 const store=createStore(reducers); export default store;
-
组件中给store传递action,触发动作 向store中存储状态
const action={ type:'ADD', data:data } store.dispatch(action)
-
视图层获取store中的状态
store.getState().xxx
-
store中的state发生变化,视图层不会自动更新
- 通过订阅subscribe把组件中的setState的代码注册过来,让视图层更新
- 每当reducers执行完毕后,状态就修改完毕,自动触发subscribe
store.subscribe(()=>{
console.log("订阅123")
this.setState({
userarr:store.getState().arr
})
})