1. 创建react项目
- 安装 npx create-react-app my_app
- 运行 npm start
2. 环境介绍
- node_modules 依赖包
- public 入口文件
- src 源代码文件
- package.json 配置文件
这些和uniapp或者vue的结构都很像的
3. React和Vue文件结构
- React和Vue的文件结构是不一样的
- Vue: 结构是( 模板 —— 逻辑 —— 样式 )
<template>
<view>
模板
</view>
</template>
<script>
export default {
data() {
return {info:"逻辑"}
}
}
</script>
<style lang="scss" scoped>
view{
样式
}
</style>
- React:JSX文件中是逻辑和模板,React需要另外引入样式
import React, { Component } from 'react';
import "./myStyle.css";
export default class test extends Component {
render() {
return (
<div>
</div>
);
}
}
4. React —— JSX语法
JSX 语法就是 JS 加上 XML,解读方式也很简单,遇到 < > 就看成 HTML ,遇到 { } 就按照JS
- 在遍历渲染的时候尤其明细jsx的语法
<ul>
{/* jsx中是遇到花括号就识别为js语法, 遇到尖括号就识别为html语法,
在遍历完成后返回一个标签,就会解析为html来渲染*/}
{
this.props.myNav.map((e,i)=>{
return <li key={i}>{e}</li>
})
}
</ul>
jsx解析的时候是按标识符来解析代码的
- jsx语法中是没办法用html的注释的,要用注释只能是 {/* */}花括号把它套起来,jsx解析到花括号就会自动解析成js语法
5. React —— 组件
super小知识
React的constructor里面的super()函数,是用来初始化this的,可以绑定事件到this上。如果用了constructor但是没有写super()函数,用到this的地方都会报错,如果没有写react会默认添加一个空的constructor里面会带上super()
react的组件名不能小写开头,首字母需要大写,其他的随意。否则会警告,且不显示
Warning: The tag <setStateDome> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.
组件文件的后缀可以是JS结尾,亦可以JSX结尾,在某些idea中JSX也会提供更多的语法提示,一个React项目是很多组件组成的,这一点在现在模块化开发的前端中,随处可见不管是vue还是啥都是把组件抽离出来,这样代码复用性也比较强。
- 组件的引入方式也和vue那些差不多,使用ES6的import引入,并且引入的组件也是以标签的形式展现。
import Nav from './Nav/Nav';
//用类的形式创建组件,还有一种是hook形式
export default class App extends React.Component{
//render渲染函数
render(){
const nav1 = ['首页','学习','导航']
const nav2 = ['吃饭','睡觉','大泡泡']
const { count } = this.state //这里使用解构赋值,需要什么数据,引入什么数据即可
return (
<>
<div>欢迎使用组件</div>
/*通过定义属性得方式,把数据传输给子组件,
因为是再函数体内,直接输入变量命即可,
自定义属性是可以传输任何类型得数据得*/
<Nav myNav={nav1} title="这是一个字符串" />
<Nav myNav={nav2} /> */}
<State />
</>
)
}
}
小技巧在渲染DOM的时候可以使用解构赋值,这样就可以少了很多代码量
6. React中使用组件局部样式
- react不像vue在样式加上scoped就可以让样式不污染全局
- react的解决方法是,引入一个 *.module.css 后缀的文件
- 之前类选择器是.main, 使用时只需要这样写className="header"。但是在组件局部样式里面,需要在要指定样式的元素上写className={styles.header}。
// js
import React, { Component } from 'react';
import "./myStyle.css"; //之前直接导入的方式
import myStyle from "./myStyle.module.css"; //命名的方式 引用样式就不会污染到环境
export default class componentName extends Component {
render() {
return (
<>
<div className={myStyle.main}></div>
</>
)
}
}
// ./myStyle.module.css
.main{
background-color: pink;
width: 150px;
height: 150px;
}
第一个是直接引入的样式,第二个是利用局部组件的方式导入的样式。 组件导入的会编译成独立样式名不会被覆盖。全局样式是会被覆盖的
7. 组件通讯方式
7.1 ,props 是父组件与子组件交互的唯一方式
- props的特性是不能修改的,只能接收、传输数据!
TypeError:无法添加属性标题,对象不可扩展
## ./Nav/Nav.jsx
export default class Nav extends React.Component{
render(){
console.log(this.props.title)
//接收数据得时候需要在props中获取
return (
<div>
<h3>{this.props.title}</h3>
<ul>
{/*
jsx中是遇到花括号就识别为js语法,
遇到尖括号就识别为html语法,在遍历完成后
返回一个标签,就会解析为html来渲染
*/}
{
this.props.myNav.map((e,i)=>{
return <li key={i}>{e}</li>
})
}
</ul>
</div>
)
}
}
7.2 父组件向子组件传值
- 在使用子组件的标签中,直接加入使用一个自定义属性就可以传递数据给子组件,子组件中直接 this.props.myData 用props可以直接获取传输过来的数据
// 父组件
<myChild myData={"父组件传递的数据"}/>
// 子组件
render() {
console.log(this.props.myData)
}
- 子组件向父组件传递数据,通过子组件调用父组件的方式,并且可以将数据当参数返回
// 父组件
render() {
return (
<div className="main">
{/* <Login/> */}
<myChild callback={this.childValue}/>
</div>
)
}
childValue = (data) =>{
console.log('app',data)
this.setState({
dataList:data
})
}
// 子组件
handleClick(){
this.props.callback(this.state.count)
}
7.3 表单控件
- react使用控件的时候需要添加上 onChange 绑定个方法
constructor(){
super();
this.state={
value:''
}
}
onChangeValue=(e)=>{
this.setState({
value:e.target.value
})
}
render(){
return <input type="text" value={this.state.value} onChange={this.onChangeValue}/>
}
- 非控制组件 Refs 可以操作DOM上的节点或者render方法中创建的元素节点,
- ref适用于这几种情况
- 管理焦点,文本选择或媒体播放
- 触发强制动画
- 集成第三方DOM库
- refs需要使用React.createRefs()创建,并通过ref属性附加到React元素中,勿过度使用Refs。
特殊情况才去操作DOM
ref可以获取绑定的DOM节点,并且操作该节点,像获取数据或者修改样式等等。。
constructor(){
super();
this.input = React.createRef();//
}
componentDidMount() {
this.input.current.style.color='pink'//修改DOM节点的样式
}
handleSubmit=(e)=>{
alert('测试看看'+this.input.current.value) //获取DOM节点的值
e.preventDefault();//取消触发默认行为
}
render() {
return (
<input type="text" ref={this.input}/>
<input type="submit" value="submit" onClick={this.handleSubmit}/>
)
}
8. 事件处理
8.1 this问题
- react中的函数方法和vue中使用的不一样,react中方法的this指向的undefined,这不是React的原因,这是JavaScript中本来就有的。如果你传递一个函数名给一个变量,然后通过在变量后加括号()来调用这个方法,此时方法内部的this的指向就会丢失。
- 可以使用箭头函数,让该函数访问外部上下文
- 可以使用bind(this)把外部的this绑定给该函数
class componentName extends Component { constructor(props){ super(props); //在构造函数中bind this.handleClick.bind(this) } //使用箭头函数,让该函数访问外部上下文 decrement=()=>{ this.setState({ count:this.state.count-=1 }) console.log(this); } //在调用的时候使用bind绑定this handleClick(){ console.log(this); } render() { return ( <> <div className={myStyle.main} onClick={this.handleClick.bind(this)}>在调用的时候使用bind绑定this</div> </> ) } }
// 这个函数是有自己的作用域的,所以需要给他bind()一下,或者使用箭头函数修改this指向
increment(){
this.setState({
// 由于这里的count是属于调用state中的count所以必须加上this.state
count:this.state.count+=1
})
console.log(this);
}
decrement=()=>{
this.setState({
count:this.state.count-=1
})
console.log(this);
}
render() {
return (
<div>
<h3>组件的state</h3>
<div>这是state渲染的---》{this.state.count}</div>
<button onClick={this.increment.bind(this)}>增加</button>
<button onClick={ this.decrement }>减少</button>
</div>
);
}
8.2 条件渲染
-
条件渲染
- react不像vue的写法,在标签中添加一个v-if的属性就可以使用判断。react的用法是类似js一样的。if...else和三元运算符
render() { const {dataList} = this.state //条件渲染,if ... else模式 let myDiv = '' if (true) { //赋值给变量的标签内容都可以在 render函数的渲染的时候,以html方式依次渲染出来 myDiv = ( <div> 测试 上 <p>1111</p> <span>2222</span> <img src="http://pic1.win4000.com/wallpaper/2020-10-21/5f8f95f2a49e2.jpg" alt=""/> </div> ) }else{ myDiv = <div>测试 下</div> } return ( <div> <ul> {myDiv} // 变量判断渲染出来的元素 { // 条件渲染三元运算(中间利用了循环遍历) dataList.length ? dataList.map((item,index)=>{ return <li key={index}>{item.title}</li> }) : <div>当前没有数据</div> } </ul> </div> ) }
-
循环遍历
- react的循环遍历渲染和vue小程序那些是有差异的,不像后者只需协商v-for这类的属性名就可以使用。由于react使用的是jsx语法,所以它的遍历渲染是类似js的循环遍历一样的
this.state= {
myList:[
{
name:'ime',
age:10,
sex:"男"
,jobs:['111','222','333']
}
]
}
<ul>
{
this.state.myList.map((element,index)=>{
return(
<li key={index}>
<span>{element.name}</span>
<span>{element.age}</span>
<span>{element.sex}</span>
<span>{
element.jobs.map((e,i)=>{
return <p style={{color:'pink'}}>e</p>
})
}</span>
</li>
)
})
}
</ul>
9. State
- 这个和微信小程序中的data或Vue的data差不多,主要是存数据,数据交互。存数据的方式和小程序很类似。
- React的this.setState 和小程序中的this.setData 是很类似的,接触过小程序的对这个存储方式并不陌生,反而和vue的直接赋值不一样。
9.1 setState更新是同步还是异步
- setState会引起视图的重绘
- 在可控的情况下是异步,非可控的情况下是同步
如果直接使用,数据还是原来的值,因为是异步的。至于为什么设计为异步,因为异步操作也是有好处的。
1. 可以显著的提升性能
如果每次调用setState都进行一次更新的话,render函数是会被频繁调用的,界面不断的被重新渲染,
这样会消耗大量的资源,导致效率变低。最好就是一次性获取多个数据,然后再批量的更新
2. 如果同步更新state,但是还没有调用render函数,那么state和props不能保持同步state和props不能保持一致性,
会在开发的过程中产很多问题
改变DOM同时又要获取最新的值
1. setState有一个回调函数,在回调函数中可以取到更新后的最新值
changeNumber(){
this.setState({
message:'哈哈'
},()=>{
console.log(this.state.message)
})
}
2. 通过componentDidUpdate函数,组件的更新会触发它,此时就是同步的新数据
componentDidUpdate(prevProps, prevState) {
console.log(this.state.message)
}
3. 使用async await也是可以改为同步
async hanlerAdd(){
await this.setState({
count:this.state.count+=1
})
console.log(this.state.count);
}
4. 使用原生DOM操作,默认也是同步
<button id="btn">减</button>
hanlerAdd(){
document.querySelector('#btn').addEventListener("click",()=>{
this.setState({
message:'哈哈'
})
console.log(this.state.message)
})
10. React生命周期函数
生命周期在现在开发中是随处可见的,和小程序、vue中的是差不多的。 函数列表:
-
componentWillMount
- 组件初始化时只调用,以后组件更新不调用,整个生命周期只调用一次,此时可以修改state。
- 和小程序、vue、uniapp的 onLoad 差不多
-
render
- react最重要的步骤,创建虚拟dom,进行diff算法,更新dom树都在此进行。此时就不能更改state了。
-
componentDidMount
- 组件渲染之后调用,只调用一次。可以在此请求数据
-
componentWillReceiveProps
- 组件初始化时不调用,组件接受新的props时调用。
-
shouldComponentUpdate
- 数据在改变之前执行(state,props)
- react性能优化非常重要的一环。组件接受新的state或者props时调用,我们可以设置在此对比前后两个props和state是否相同,如果相同则返回false阻止更新,因为相同的属性状态一定会生成相同的dom树,这样就不需要创造新的dom树和旧的dom树进行diff算法对比,节省大量性能,尤其是在dom结构复杂的时候
-
componentWillUpdata(nextProps, nextState)
- 组件初始化时不调用,只有在组件将要更新时才调用,此时可以修改state
11.PropsTypes 类型检测
- 类似于ts的类型检测,使程序更加健壮。类型检测的好处在于,随着程序越来越大,可以通过它捕获大量的错误。
- PropsTypes给props做类型检测
import React, { Component } from 'react';
import PropsType from 'prop-types';
export default class PropsTypeDemo extends Component {
render() {
return (
<div>
{this.props.title}
</div>
);
}
}
PropsTypeDemo.propTypes = {
title:PropsType.bool
}
// 如果传递的数据不对,则报错 Failed prop type:
- props types提供了几种类型的校验
// 声明属性为js原生类型,这些属性都是可选的
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
//任何可被渲染的元素,包含数字、字符串、元素和数组
optionalNode: PropsTypes.node,
// 一个React元素
optionalElement: PropsTypes.element,
//一个React元素类型
optionalElementType:PropsTypes.elementType,
//一个对象可以是几种类型中的任意一个类型
optionalUnion: PropsTypes.oneOfType([
PropsTypes.string,
PropsTypes.number,
PropsTypes.instanceOf(Message)
])
//任意类型的必需数据
requiredAny: PropsTypes.any.isRequired,
//自定义验证器,失败时返回Error对象。但是在 console.warn和抛出异常中是不可用的,onOfType 不会生效
customProp:function(props, propName, componentName){
if(!/matchme/.test(props[propName])){
return new Error(
`验证信息有误`
)
}
}
### 更多的用法可以查看官方文档
12. 路由
路由好比是打车,只要说出了特定的地名,老司机就能带你到那个地方。
- history 模式
- window 的 history 提供了对浏览器历史记录的访问功能,并且它暴露了一些方法和属性,让你在历史记录中自由的前进和后退,并且在 H5 中还可以操作历史记录中的数据。
history.back() ; // 在历史记录是后退 history.forward(); // 在历史记录中前进 history.go(-1); // 移动到指定的历史记录点
- hash 模式
- hash的改变,不会导致浏览器的刷新
13. 网络请求,Fecth
- fetch和axios都是基于promise设计的,可以先把promise的知识学一学有助于对这个库的理解更加透彻。
- fetch写起来代码会更加简洁(这里是使用了es6的箭头函数以及 promise的 .then 来写的)
componentDidMount() {
// GET请求
fetch("http://iwenwiki.com/api/blueberrypai/getIndexBanner.php").then(res => res.json())
.then(data =>{
if( data.success ){ //这里是后端返回的成功码
console.log(data);
}
}).catch(err => console.log('错误信息',err))
/* POST请求
* Ajax的参数是对象键值对的
* Fetch的body 是字符串类型
* "user_id=123123&password=123123&verification_code=123123"
Fetch识别的是这样子的类型请求参数
* import qs from "querystring";
* 引入nodeJs的对象转换字符串方法
*/
fetch("http://iwenwiki.com/api/blueberrypai/login.php",{
method:"POST",
body: qs.stringify({ // 引入了nodeJs的对象转字符串方法 qs.stringify
user_id:123123,
password:123123,
verification_code:123123
}),
headers:{
"Content-Type":'application/x-www-form-urlencoded',
"Accept":"application/json,text/plain,*/*"
}
}).then(res=>res.json())
.then(data=>{
console.log(data);
}).catch(err => console.log(err))
}
}
-
网络请求的过程中是会出现跨域问题的,端口不同就会出现跨域问题,一般这种问题可以让后端去解决。不过前端也是有解决的方法。
- 步骤一 npm install http-proxy-middleware
- 步骤二 npm run eject
- 步骤三 src下创建一个 setupProxy.js文件
//0.X 版本的引入这个 //const proxy = require('http-proxy-middleware'); //1.X 版本的引入这个 const { createProxyMiddleware } = require("http-proxy-middleware"); module.exports = function(app) { app.use( createProxyMiddleware("/api", { target: "http://localhost:3000", changeOrigin: true }) ); };
- 步骤四 start.js里面做一下配置
// 在scripts文件夹里面的start.js 搜索 devServer const serverConfig = createDevServerConfig( proxyConfig, urls.lanUrlForConfig ); const devServer = new WebpackDevServer(compiler, serverConfig); //在它下面添加上即可 require('../src/setupProxy')(devServer);//文件路径按自己的来