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