React原理和JSX

React是Facebook开发出的一款JS框架 Facebook认为MVC无法满足他们的扩展需求 React发布于2013年

特点:

  • react不使用模板

  • react不是一个MVC框架

  • 响应式(数据更新)

  • 一个轻量级的JS库

原理

  • 虚拟DOM react把DOM抽象成一个JS对象

  • diff算法

  • 虚拟DOM确保对界面上真正发生的部分进行实际DOM操作(先更改虚拟DOM再将真正发生变化的部分进行实际DOM操作)(虚拟DOM的存在是因为每次都进行完整DOM操作非常浪费资源)

  • 逐层次进行节点比较

React 开发环境的搭建

  • react.js 核心文件 npm install --save react

  • react-dom.js /react-native.js 渲染页面中的DOM 当前文件依赖于react核心文件 npm install --save react-dom

  • babel.js ES6转换为ES5 JSX语法转换为JavaScript 先进浏览器进行代码兼容 npm install --save babel-standalone

  • babel在script标签内引用时 一定要写 type=text/babel

  • 一个页面中需要有一个根节点(div) 这个节点下的内容就会被react管理

JSX:JavaScript XML JavaScript的扩展语法

优点:

  • 执行效率更快
  • 类型安全,编译过程中能及时发现错误
  • 在用JSX的时候编写模板会更加简单和快速

生命周期

1. 挂载卸载过程

2. 更新过程

3. React新增的生命周期(个人补充)

语法:

  • 注释:{/**/}

  • JSX中的HTML标签必须按照W3C标准规范 标签必须闭合

  • JSX写多行标签需要一个父标签包裹(div)最好用括号括起来

  • 如果想在JSX中使用表达式只需把表达式放入{ }中

  • //例如:
    <div id="reactDom"></div>
    
    let text = "HELLOREACT!";
    
    let myDom = (<div>{text}</div>)
    
    ReactDOM.render(myDom, document.getElementById("reactDom"));
    //即可渲染成功
    



    - 计算

    - ```javascript
    let num = 9527;

    //mYDom里:
    <div>{num + 1}</div>
  • 函数

  • function fun(obj) {
    
    //return"名字是:"+obj.name+"年龄是:"+obj.age;
    
        return `名字是:${obj.name}年龄是:${obj.age}`;(ES6写法)
    
    }
    
    //myDom里:
    
    <div>{fun(user)}</div>
    



    - 三目运算

    - ```javascript
    let phone = 4999;

    //myDom内:

    <div>手机的价格是{phone}--- ---{phone > 4000 ? "很贵" : "很便宜"}</div>
  • 渲染数组

  • var arr = [
            <p>新闻列表</p>,
            <p>新闻列表</p>,
            <p>新闻列表</p>,
            <p>新闻列表</p>,
            <p>新闻列表</p>,
            <p>新闻列表</p>
        ]
        //myDom里:
        < div > {arr}</div>
    



    - 属性设置

    - ```javascript
    let link = "点我去百度";
    let linkUrl = "https://www.baidu.com";
    //myDom里:
    <a href={linkUrl}>{link}</a>
  • 样式设置

  • //注意style接收的是一个对象
    
    let modstyle = {color: "red", fontSize: 30, backgroundColor: "pink"};
    
    //myDom里:
    
    <div style = {modstyle} > 修改我的样式 < /div>
    



    - class类设置

    - ```javascript
    //注意在JSX中不能使用class 这个属性是js的关键字 所以用className

    <div className="class1">class样式</div>

    //<style>内

    .class1{

    font-size:30px;

    background-color:pink;

    color:red;

    }

教程

遍历列表

map遍历

//react数组遍历渲染map()方法
//必须填入key值且独一无二建议index
//若要换行用()
let arr = ["吃饭", "睡觉", "打豆豆"];
let myDom = arr.map((item, index) => {
return (
<p key = {index} > {item}
</p>)
})

for in 遍历

let arr = ["吃饭", "睡觉", "打豆豆"];

function fun() {
let newarr = [];
for (let index in arr) {
newarr.push( <p key = {index} > {arr[index]}
</p>)
}
return newarr;
}

遍历对象

let obj = {
name: "LERONCHAO",
age: 20,
sex: "man"
}

let myDom = (
Object.keys(obj).map((value, index) => {
return <p>{value}:{obj[value]}</p>
})
)

ReactDOM.render(myDom, document.getElementById("reactDom"));

设置点击样式、点击事件onClick

let arr = ["吃饭", "睡觉", "打豆豆"];
let index = -1;

function fun() {
let myDom = arr.map((v, i) => {
return <p style={{color: i == index ? 'red' : ''}} key={i} onClick={() => {
index = i;
render();
}}>{v}</p>
});
return myDom
}

function render() {
ReactDOM.render(fun(), document.getElementById("reactDom"));
}

render();

封装组件(组件名称第一个字母必须大写)

function Com()
{
return (
<div>这是一个无状态组件</div>
)
}

let myDom = (<div>
<Com/>
</div>);

ReactDOM.render(myDom, document.getElementById("reactDom"))

父子组件嵌套渲染

function ComA()
{
return (
<div>
我是子组件1
</div>
)
}
function ComB()
{
return (
<div>
我是子组件2
</div>
)
}
function ComC()
{
return (
<div>
我是子组件3
</div>
)
}
function MyCom()
{
return (
<div>
<ComA/>
<ComB/>
<ComC/>
</div>
)
}

let myDom = <div>
<MyCom/>
</div>

ReactDOM.render(myDom, document.getElementById("reactDom"))

创建类组件

class Com extends React.Component
{
render()
{
return (
<div>类组件</div>
)
}
}

let myDom = <div>
<Com/>
</div>

ReactDOM.render(myDom, document.getElementById("reactDom"))

Props传值(组件对外的接口)

//无状态组件props传值
function Com(props)
{
return (
<div>
外部传入props的数据是:{props.text}:{props.num}
</div>
)
}

//类组件props传值
class Com extends React.Component
{
render()
{
return (
<div>
外部传入props的数据是:{this.props.text}:{this.props.num}
</div>
)
}
}
//写法一
let myDom = <div>
<Com text="LEBRONCHAO" num="20"/>
{/*此处也可以通过{}写入变量*/}
</div>;

//写法二
let obj = {
text: "LEBRONCHAO",
num: "20"
};
let myDom = <div>
<Com {...obj}/>
{/*ES6扩展运算符写法
...obj即text="LEBRONCHAO",num="20"*/}
</div>;
ReactDOM.render(myDom, document.getElementById("reactDom"))

Props默认值的编写

Com.defaultProps = {
name: "LEBRONCHAO",
num: "20"
};
//(类组件用法相同)
// 类组件第二种写法:
class Com extends React.Component
{
Static defaultProps
{
name:“第二种方式",
num:20
}
render(){
return (
<div>
外部传入props的数据是:{this.props.name}:{this.props.num}
</div>
)
}
}

//15.x react版本写法
function Com(props)
{
props.name = props.name || "LEBRONCHAO";
props.num = props.num || "20";
return (
<div>props的数据是:{props.name}:{props.num}</div>
)
}


function Com(props)
{
return (
<div>props的数据是:{props.name}:{props.num}</div>
)
}
let myDom = <div>
<Com/>
</div>;
ReactDOM.render(myDom, document.getElementById("reactDom"))

Props数据类型验证 上线模式请取消 大小写严格要求

  • 引用prop-types库 npm install --save prop-types
Com.propTypes={

num:PropTypes.number //验证num是否为数字

}
//(类组件用法相同)
  • propstypes验证不会影响页面展示 控制台会报错

  • 除了验证数据类型还可以设置 isRequired必须填写属性可串写

  • PropTypes.number.isRequired具体可检查类型见react官网

子组件遍历渲染父组件传入列表

制造不断点击LOL英雄列表消失、显示效果

let bool = false;
let arrData = ["寒冰", "盖伦", "蛮王"]
let Father = () => {
return (<div>
<h1 onClick={() => {
bool = !bool;
fun();
}}>LOL英雄展示</h1>
<Son arr={arrData}/>
</div>)
};
let Son = (props) => {
return (
<div style={{display: bool ? "block" : "none"}}>
{
props.arr.map((v, i) => {
return <p key={i}>{v}</p>
})
}
</div>
)
};

function fun() {
ReactDOM.render(<Father></Father>, document.getElementById("reactDom"))
}

fun()

State状态

  • 使用state不能使用无状态组件(函数式)

state和props的区别:

  • state可变 props只负责传递不可更改

  • props是组件对外的接口 state是组件队内的接口

  • 组件内可以引用其他组件 组件之间的引用就形成了一个树状结构

  • 下层组件需要使用上层组件的数据使用state

  • 组件除了使用上层组件传递的数据外 自身有可能又需要管理的数据 state进行内部数据管理

  • react中我们只需要关心的是数据 当数据改变的时候页面就自动发生改变

  • 状态等同于页面中的数据 状态/数据改变了页面中对应的数据绑定内容就会被react自动改变

  • 声名式渲染 一切的数据改变操作都不用我们关心 只需要我们声明好数据 react自动对于数据的改变进行相应的改变

class MyCom extends React.Component {
constructor(props) {
//ES6中不管子类写不写constructor在new实例的时候都会补上constructor
//我们可以不写constructor如果写了之后就必须在其中super()就是指向父类的构造方法
super(props);
//如果想在constructor中使用props,super和constructor中必须写上props
this.state = {
name: "LEBRONCHAO",
tag: "<p>我是一个标签</p>"
}
}

render() {
return (
<div>
{/*this.setState({key:value})异步的react就会自动触发render进行数据的渲染*/}
<div>我的名字是{this.state.name}</div>
<button onClick={(() => {
this.setState({name: "JAMES"})
})}>点击我改变数据
</button>
<div>{this.state.tag}</div>
{/*如果要插入的值是html,使用dangerouslySetInnerHTML={{__html:""}}属性*/}
<div dangerouslySetInnerHTML={{__html: this.state.tag}}></div>
</div>
)
}
}

let myDom = (<div>
<MyCom/>
</div>);

ReactDOM.render(myDom, document.getElementById("reactDom"))

转发refs

  • react当中提供了一个ref的数据(无状态组件无法使用)

  • 表示当前组件真正的实例的引用 他会返回绑定当前属性的元素

  • 标识内部组件方便查找

React中三种三种方式进行ref引用

  • 字符串的形式

  • 回调函数

    在dom节点上或组件上挂载函数 函数的入参形参 是dom节点 他的效果和字符串的方式一样都是获取值得引用

  • React.createRef() react16.3新提供的方式

    把值赋给一个变量 通过ref挂载在节点或者组件上 使用ref的current属性拿到这个节点

class Com extends React.Component {
constructor(props) {
super(props);

this.myRef = React.createRef();
}

fun = () => {
//1.
//console.log(this.refs.demo.value);

//2.
//console.log(this.textinput.value);

//3.
//console.log(this.myRef.current.value);

};

render() {
return (
<div>
{/*1.字符串方法
<input type="text" ref="demo" placeholder="请输入"/>
*/}

{/*2.挂载函数方法
<input type="text" ref={(input)=>{this.textinput=input}}/>
*/}
{/*3.React.createRef()方法
<input type="text" ref={this.myRef}/>
*/}
<button onClick={this.fun}>点击我得到输入框的值</button>
<div>{this.state.demo}</div>
</div>
)
}

}

ReactDOM.render(<Com/>, document.getElementById("reactDom"))

React事件处理

  • react事件处理使用小驼峰命名法 在绑定函数的时候不能加() 加了函数会立即执行

  • 箭头函数默认指向当前类组件 其他函数需要通过以下方法更改this

修改this指向方法:

  • bind方式原地 绑定

  • 函数通过箭头函数进行创建

  • constructor中提前绑定

  • 把时间的调用写成箭头函数的调用方式(加圆括号)

class Com extends React.Component {
constructor(props) {
super(props);
this.func = this.func.bind(this)
}

funa() {
console.log(this)
}

funb = () => {
console.log(this)
}

func() {
console.log(this)
}

fund() {
console.log(this)
}

fune = (i, event) => {
console.log(i);
console.log((event));

}

render() {
return (
<div>
<button onClick={this.funa.bind(this)}>bind原地绑定</button>
<button onClick={this.funb}>通过箭头函数创建</button>
<button onClick={this.func}>constructor中提前绑定</button>
<button onClick={() => {
this.fund()
}}>事件写成箭头函数调用
</button>
<h1>函数实参传递</h1>
<button onClick={this.fune.bind(this, "我是参数1")}>bind方法</button>
<button onClick={(event) => {
this.fune("我是参数2", event)
}}>箭头函数方法
</button>
</div>
)
}
}

ReactDOM.render(<Com/>, document.getElementById("reactDom"))

条件渲染

  • 根据状态变化 只渲染其中一部分

  • JSX中不允许if语句

class Com extends React.Component{
constructor(props){
super(props);
this.state={
bool:true
}

}
fun(){
this.setState({bool:!this.state.bool})
}
render(){
let text;
if(this.state.bool){
text="你好";
}
else{
text="你坏";
}
return(
<div>
<h1>三目运算符方法</h1>
<p>{this.state.bool?"你好":"你坏"}</p>
<h1>if判断方法</h1>
<p>{text}</p>
<button onClick={this.fun.bind(this)}>条件渲染</button>
</div>
)
}
}

ReactDOM.render(<Com/>,document.getElementById("reactDom"))

状态提升

  • 多个组件需要反映相同的变化数据 提升到他们最近的一个父组件中

  • 多个子组件需要利用到对方状态的情况下

  • 在父组件中声明状态变量和改变

	class Demoa extends React.Component {
constructor(props) {
super(props);
this.state = {}
}

render() {
return (
<div>
我是组件一:{this.props.text}
</div>
)
}
}

class Demob extends React.Component {
constructor(props) {
super(props);
this.state = {}

}

render() {
return (
<div>
我是组件二:{this.props.text}
</div>
)
}
}

class Com extends React.Component {
constructor(props) {
super(props);
this.state = {
text: "我是两个组件都需要的数据"
}

}

fun() {
this.setState({text: "数据已修改"})
}

render() {
return (
<div>
父组件
<Demoa text={this.state.text}/>
<Demob text={this.state.text}/>
<button onClick={this.fun.bind(this)}>修改数据</button>
</div>
)
}
}

ReactDOM.render(<Com/>, document.getElementById("reactDom"))

React脚手架使用

  • npm install -g create-react-app 全局安装脚手架

  • create-react-app --version 查看版本

  • 在需要创建项目的目录下 create-react-app 项目名(英文) 创建项目

  • npm start 启动项目

工程文件介绍:

  • readme.md --项目介绍自定义一些项目信息以及简单使用

  • package.json --dependencies下管理生产环境所用的包及版本

    • devDependencies里得插件只用于开发环境,不用于生产环境
    • name项目名称
    • version项目版本
    • scripts项目启动等功能设置
  • package.lock.json —上线锁定版本信息

  • gitignore git的选择性上传的配置文件,比如一会要介绍的node_modules文件夹,就需要配置不上传。

  • public —公共文件,里边有公用模板和图标等一些东西

public文件夹介绍

这个文件都是一些项目使用的公共文件,也就是说都是共用的,我们就具体看一下有那些文件吧。

  • favicon.ico : 这个是网站或者说项目的图标,一般在浏览器标签页的左上角显示。

  • index.html : 首页的模板文件,我们可以试着改动一下,就能看到结果。

  • mainifest.json:移动端配置文件。

  • src 主要代码编写文件,这个文件夹里的文件对我们来说最重要,都需要我们掌握

src文件夹介绍

这个目录里边放的是我们开放的源代码,我们平时操作做最多的目录。

  • index.js : 这个就是项目的入口文件。
  • index.css :这个是index.js里的CSS文件。
  • app.js : 这个文件相当于一个方法模块,也是一个简单的模块化编程。
  • serviceWorker.js: 这个是用于写移动端开发的,PWA必须用到这个文件,有了这个文件,就相当于有了离线浏览的功能。

src内建立Components文件夹写组件

import React,{Component,Fragment} from 'react'     //此处引入
export default class Name extends Component{
render(){
return(
//若不想建立div包裹可用<></> 或者<Fragment><Fragment/>(要引入)
<div>内容<div/>)
}
}

图片引用的三种方式

例如1.jpg

  • 直接把图片放在public中 src=“1.jpg”

  • 把图片通过引用方式引入

Import ImgA from ‘路径’ src={ImgA}

  • src={require(“路径”)}

    • props验证需引入 import PropTypes from ‘prop-types’

    • props父传子使用和state使用相同

子传父写法

//子组件内:
<button onClick={() => {
this.props.fufun(this.state.ziText)
}}>点我子传父</button>
//父组件内:
<News fufun={(text) => this.dataFun(text)}/>
<div>{this.state.ziText}</div>

dataFun = (text) => {
this.setState({ziText: text})
}

同级组件传值pubsub方法

//接受组件写法:
PubSub.subscribe("eve", (msg, data) => {
this.setState({
phone: data
})
})

//发送组件写法:
PubSub.publish("eve", this.state.num);

Json-server 和 axios

  • 全局安装 npm install json-server -g
  • 启动 cd 到对应文件夹(含json的文件)下
  • 控制台 json-server json文件名 --port 4000(修改端口,默认端口用作前端开发)

axios

//全局安装
//npm install --save axios
//在页面中引用axios
//方法不止于get
axios.get("url").then((res)=>{
this.setState({
data:res.data
})
})

React解决跨域

  • 正向代理—开发环境

    一个位于客户端和目标服务器之间的代理服务器 为了获取到目标服务器的内容客户端向代理服务器发送一个请求 代理服务器帮助我们去目标服务器里获取数据并返回给我们

  • 反向代理—上线环境

    可以通过代理服务器来接受网络上的请求连接 然后将这个请求转发给内部的网络服务器上并且把这个服务器上的得到的数据返回给请求的客户端

Node/react-script/config/webpackDevServer.config.js下

找到proxy

proxy:{

"/api":{

target:"url",

chaneOrigin:true,

"pathRewrite":{

"^api":"/"
}
}
}

axios.get("/api").then((res)=>{
//执行函数回调
})

React 路由

react-router 只提供一些核心API

react-router-dom 更多选项

路由模式

  • hash模式 HashRouter 带#号 刷新的时候页面不会丢失

  • browser BrowserRouter 历史记录模式 无#号 他是通过历史记录api来进行路由切换刷新会丢失 本地模式不会

  • 下载 npm install --save raect-router-dom

  • index.js中引用 import {BrowserRouter}/{HashRouter} from ‘react-router-dom’

    • 用BrowserRouter/HashRouter包裹APP标签
  • 使用路由的组件 引入路由 import {Route,Link,NavLink,Switch,Redirect} from ‘react-router-dom’

  • 配置Route (通过Route组件 写 path 、component实现路由)

  • 配置Link/NavLink 通过Link/NavLink组件写to路径 要和Route中的path相同

  • NavLink给动态地给选中的导航添加active类名 方便写样式

  • Redirect标签写默认首页 from”/“ to”/组件名“ 并写exact属性精准匹配

  • path写入”/“每次都会默认渲染 添加exact属性精准匹配可消除

  • 多个相同的Route渲染 用Switch标签(从react-router-dom引入)包裹可避免重复

  • Route相当于路由页面

  • Link相当于链接按钮

import React from 'react';
import './App.css';
import Home from "./Components/Home";
import {NavLink, Redirect, Route} from "react-router-dom";
import Page1 from "./pages/Page1";
import Page2 from "./pages/Page2";
import Page3 from "./pages/Page3";

<div>
<NavLink to="/Page1">Page1</NavLink>
<NavLink to="/Page2">Page2</NavLink>
<NavLink to="/Page3">Page3</NavLink>
</div>

<Route path="/Page1" component={Page1}/>
<Route path="/Page2" component={Page2}/>
<Route path="/Page3" component={Page3}/>
<Redirect from="/" to="/Page3" exact></Redirect>


//子路由配置(Page1下)
import React, {Component} from 'react';
import {Route, NavLink, Redirect} from 'react-router-dom'
import Child1 from "./childpages/Child1";
import Child2 from "./childpages/Child2";
import Child3 from "./childpages/Child3";

class Page1 extends Component {
render() {
return (
<div>
Page1
<NavLink to="/Page1/Child1">Child1</NavLink>
<NavLink to="/Page1/Child2">Child2</NavLink>
<NavLink to="/Page1/Child3">Child3</NavLink>

<Route component={Child1} path="/Page1/Child1"></Route>
<Route component={Child2} path="/Page1/Child2"></Route>
<Route component={Child3} path="/Page1/Child3"></Route>
<Redirect from="/" to="/Page1/Child3"></Redirect>

</div>
);
}
}

export default Page1;

高阶组件

withRouter从react-router-dom中引入 用withRouter包裹App withRouter(App)(export 的地方)

(HOC 参数是一个组件 同时返回的也是一个组件 这类组件我们称为高阶组件 就是让不是路由切换的组件也具有路由切换的三个属性location history match)

//监控路由变化 

history.listen((link)=>{

Cosole.log(link)

})

编程式导航

  • APP组件参数写入props

<button onClick={()=>{props.history.push"path"}}>点我去path<button>

  • path替换路径例如/Home

编程式路由传参

  • params方式进行传参

    • 在Route组件中path属性路径后面写上 /:参数名

    • 发送参数在NavLink/Link标签的to后写 /参数

    • 在对应组件接收页面接收 props.match.params.参数名

    • 优势:刷新地址,参数依然存在

    • 缺点:只能传递字符串,并且参数过多的时候url会变得比较丑陋

  • query方式传参

    • 不需要再Route组件中配置

    • 直接在NavLink/Link标签的to属性中发送数据

    • 对应组件中用this.props.location.query.参数名

    • NavLink/Link标签写法:

      <NavLink to={{pathname:"路径",query:{参数名:“参数”}}}>

      此写法可尝试传多参 推荐

掘金:前端LeBron

知乎:前端LeBron

持续分享技术博文,关注微信公众号👇🏻

img