React官网
- 英文官网: reactjs.org/
- 中文官网: react.docschina.org/
介绍描述
- 用于动态构建用户界面的 JavaScript 库(只关注于视图)
- 由Facebook开源
声明式编码
-
命令式编码
- document.body.style.background = '#ccc'
-
声明式编码
- this.setState({bg: '#ccc'})
React的特点
- 声明式编码
- 组件化编码
- 高效(优秀的Diffing算法)
- 引入 jsx 语法
- React Native 编写原生应用
React高效的原因
- 使用虚拟(virtual)DOM, 不总是直接操作页面真实DOM。
- DOM Diffing算法, 最小化页面重绘。
相关js库
- react.js:React核心库。
- react-dom.js:提供操作DOM的react扩展库。
- babel.min.js:解析JSX语法代码转为JS代码的库。
虚拟DOM与真实DOM
- React提供了一些API来创建一种 “特别” 的一般js对象
const VDOM = React.createElement('xx',{id:'xx'},'xx')
上面创建的就是一个简单的虚拟DOM对象
- 虚拟DOM对象最终都会被React转换为真实的DOM
- 我们编码时基本只需要操作react的虚拟DOM相关数据, react会转换为真实DOM变化而更新界。
jsx
- 全称: JavaScript XML,是react定义的一种类似于XML的JS扩展语法,其本质是React.createElement(component, props, ...children)方法的语法糖
- 作用: 用来简化创建虚拟DOM
写法:var ele = <h1>Hello JSX!</h1>
//JSX 注意点
1. 它不是字符串, 不需要添加引号,它最终产生的就是一个JS对象,不能用引号包裹, 可以用『小括号』包裹
2. 结构中混入 js 表达式需要使用 {} JSX 与『JS表达式』拼接时, 需要用到 『{}』
3. 设置 class 属性时需要使用 className
4. 指定内联样式时,需要使用对象的形式 style={{color:'red'}}
5. 所有结构必须要有根标签
6. 所有标签必须要闭合,对于自闭和标签 img input 注意,也必须要闭合 <img></img> <img /> 两种写法都可以
7. 标签名
1) 若首字母为小写,则将其转化为 html 同名标签。若找不到同名标签则报错
2) 若首字母为大写,则回寻找对应的组件,找到就渲染,找不到就报错
babel.js的作用
如果编写的是 HTML 标签, 标签名首字母一定要『小写』, 如果编写的是组件标签, 标签名首字母一定要『大写
1) 浏览器不能直接解析JSX代码, 需要babel转译为纯JS的代码才能运行
2) 只要用了JSX,都要加上type="text/babel", 声明需要babel来处理
关于 {} 中能放的内容
JS 的表达式. (有返回值)
- 四则运算 1 + 1 2*2 3/3 4-3 %
- 变量 let b = a;
- 三元表达式
- 函数调用 function fn(){} fn();
- 直接量
{} 中放置的表达式, 可以返回 『数字』 『字符串』 『虚拟DOM』
渲染虚拟DOM(元素)
-
1. 语法: ReactDOM.render(virtualDOM, containerDOM) -
作用: 将虚拟DOM元素渲染到页面中的真实容器DOM中显示
-
参数说明
参数一: 纯js或jsx创建的虚拟dom对象
参数二: 用来包含虚拟DOM元素的真实dom元素对象(一般是一个div)
条件渲染
let isVip = true; // is 是否 vip 贵宾
//声明一个函数
function showAd(){
if(isVip){
return null;
}else{
return <div id="ad"></div>;
}
}
//创建虚拟 DOM
let vDOM = <div>
{/*1. 第一种 &&*/}
{!isVip && <div id="ad"></div>}
{/*2. 第二种 三元表达式*/}
{isVip ? null : <div id="ad"></div>}
{/*3. 第三种 函数调用*/}
{showAd()}
{<div>abc</div>}
</div>;
//渲染
ReactDOM.render(vDOM, document.querySelector("#root"));
列表渲染
//准备数据
let data = ['vue', 'react', 'angular'];
// let arr = [<span>123</span>, <span>456</span>, <span>789</span>];
//创建虚拟 DOM
let vDOM = <div>
<h2>前端三大框架</h2>
<ul>
{
data.map((item, index) => {
return <li key={index}>{item}</li>;
})
}
</ul>
</div>;
ReactDOM.render(vDOM, document.querySelector("#root"));
模块与组件、模块化与组件化的理解
模块
- 理解:向外提供特定功能的js程序, 一般就是一个js文件
- 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。
- 作用:复用js, 简化js的编写, 提高js运行效率
组件
- 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)
- 为什么要用组件: 一个界面的功能更复杂
- 作用:复用编码, 简化项目编码, 提高运行效率
模块化
当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
组件化
当应用是以多组件的方式实现, 这个应用就是一个组件化的应用
//函数式组件
function Top(){ //首字母一定要大写
//返回虚拟 DOM 对象
return <header></header>
}
//内容区组件
function Content(){
return <main></main>
}
//底部区组件
function Bottom(){
return <footer></footer>
}
// <Top></Top> React 会自动寻找与 Top 同名的『函数』, 执行函数返回『虚拟 DOM 对象』, 最终渲染
function App(){
return <div>
<Top></Top>
<Content />
<Bottom />
</div>;
}
ReactDOM.render(<App />, document.querySelector("#root"));
//类式组件
class Top extends React.Component{
//这里 render 的名字是固定的
render(){
return <header></header>;
}
}
//内容区组件
class Main extends React.Component{
//这里 render 的名字是固定的
render(){
return <main></main>;
}
}
//尾部区组件
class Footer extends React.Component{
render(){
return <footer></footer>;
}
}
//创建虚拟 DOM 对象
// <Top></Top> 寻找与 Top 同名的类, 实例化对象, 并调用 render 方法得到虚拟 DOM 对象, 最终渲染
class App extends React.Component{
render(){
return <div>
<Top></Top>
<Main />
<Footer/>
</div>
}
}
ReactDOM.render(<App />, document.querySelector("#root"));
组件三大核心属性1: state
-
state是组件对象最重要的属性, 值是对象(可以包含多个key-value的组合)
-
组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件)
-
状态. state 是类式组件实例对象中的『一个属性』. 初始值是对象, 里面可以『存储属性数据』.
特点: 当对 state 对象中的属性进行设置的时候, 会自动重新调用 render 方法, 渲染组件
强烈注意
- 组件中render方法中的this为组件实例对象
- 组件自定义的方法中this为undefined,如何解决?
- 强制绑定this: 通过函数对象的bind()
- 箭头函数
- 状态数据,不能直接修改或更新
组件三大核心属性2: props
- 每个组件对象都会有props(properties的简写)属性
- 组件标签的所有属性都保存在props中
props作用
props 是 properties 的缩写, 单词本意『属性』. 可以接收组件外传入组件的数据, 实现『组件的解耦与复用』
- 通过标签属性从组件外向组件内传递变化的数据
- 注意: 组件内部不要修改props数据
组件三大核心属性 refs与事件处理
理解
ref 单词『reference』的缩写, 『引用』. ref 是类式组件实例对象中的一个属性, 可以帮忙快速获取『元素对象 (DOM对象)』
组件内的标签可以定义ref属性来标识自己
ref 使用方式选择
- 字符串
- 回调函数 √
- createRef √
事件处理
- 通过onXxx属性指定事件处理函数(注意大小写)
- React使用的是自定义(合成)事件, 而不是使用的原生DOM事件
- React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)
- 通过event.target得到发生事件的DOM元素对象
React 中事件回调的 this
默认是指向 『undefined』
谈谈你对 xxx 的理解 (3w1h)
- 是什么
- 作用
- 特点
- 怎么用
谷歌应用商店网址
vscode
ctrl + d 选中相同的文本内容
事件
- 鼠标事件 click dblclick mouseover mouseenter mousemove mouseout mouseleave mousedown mouseup
- 键盘事件 keydown keyup keypress
- 滚轮事件 mousewheel
- 表单事件 focus blur submit (e.preventDefault)
- 文档事件 load beforeunload
绑定方式
-
<div onclick="alert(123)"></div> -
div.onclick = function(){}
-
div.addEventListener('click', function(){});
- unicode 是字符集
- utf-8 是 unicode 的编码方式
e.target
是事件触发时, 所操作的『元素』
鼠标事件小结
- 事件源 e.target
- 阻止默认行为 e.preventDefault(放在第一行)
- 阻止冒泡(阻止捕获) e.stopPropagation
- 获取鼠标的位置 e.clientX e.pageX e.pageY
react 事件补充
- 合成事件
- 效率高 事件绑定在了 document 身上, 事件委派.
- 兼容性好
高阶函数
函数.
高阶函数: 是一种函数, 如果返回了一个函数, 或者接受函数类型的参数, 就被称之为是『高阶函数』
情况:
- Promise
- then
- 数组方法 map forEach some every filter find
- 定时器 setTimeout setInterval
- bind
阻止默认行为,阻止ctrl+c的功能
//阻止右键菜单事件
window.oncontextmenu = function(e){
e.preventDefault();
}
//阻止ctrl+c的功能(不能复制)
window.onkeydown = function(e){
if(e.keyCode === 67 && e.ctrlKey) {
e.preventDefault();
}
}
//受控组件,获取b
class App extends React.Component{
//1. 声明状态
state = {
user: '',
pass: '',
code: '',
phone: ''
}
render(){
return <div>
<h2>登录</h2>
<form action="">
{/**2. 为表单元素 设置 value 与 onChange **/}
用户名: <input type="text" value={this.state.user} onChange={this.saveData('user')} /><br />
密码: <input type="password" value={this.state.pass} onChange={this.saveData('pass')} /><br />
验证码: <input type="text" value={this.state.code} onChange={this.saveData('code')} /><br />
手机号: <input type="text" value={this.state.phone} onChange={this.saveData('phone')} /><br />
<input type="submit" value="登录" onClick={this.login} />
</form>
</div>
}
//方法
saveData = (type) => {
return (e) => {
this.setState({
[type]: e.target.value
});
}
}
login = (e) => {
//获取表单项的值
e.preventDefault();
console.log(this.state.user);
console.log(this.state.pass);
}
}
ReactDOM.render(<App />, document.querySelector("#root"));
生命周期
理解
- 组件从创建到死亡它会经历一些特定的阶段。
- React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。
- 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。
重要的勾子
- render:初始化渲染或更新渲染调用
- componentDidMount:开启监听, 发送ajax请求
- componentWillUnmount:做一些收尾工作, 如: 清理定时器
经典面试题
/**
经典面试题:
1). react/vue中的key有什么作用?(key的内部原理是什么?)
2). 为什么遍历列表时,key最好不要用index?
3). 请你简单的聊聊DOM的Diffing算法?
1. 虚拟DOM中key的作用:
1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时,key起着极其重要的作用。
2). 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
(1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
(2).若虚拟DOM中内容变了, 则更新之前的真实 DOM
b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
根据数据创建新的真实DOM,随后渲染到到页面
2. 用index作为key可能会引发的问题:
1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
2. 如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。
3. 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,
仅用于渲染列表用于展示,使用index作为key是没有问题的。
3. 开发中如何选择key?:
1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2.如果确定只是简单的展示数据,用index也是可以的。
*/
React脚手架创建项目并启动
第一步,全局安装:npm i -g create-react-app
第二步,切换到想创项目的目录,使用命令:create-react-app hello-react
第三步,进入项目文件夹:cd hello-react
第四步,启动项目:npm start
消息订阅发布机制
-
工具库: PubSubJS
-
下载: npm install pubsub-js --save
-
使用:
import PubSub from 'pubsub-js' //引入
PubSub.subscribe('name', function(data){ }); //订阅
PubSub.publish('name', data) //发布消息
React路由
路由分类
后端路由:
理解: value是function, 用来处理客户端提交的请求。
注册路由: router.get(path, function(req, res))
工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
前端路由:
浏览器端路由,value是component,用于展示页面内容。
注册路由:
工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件
React中路由的使用
//安装react-router-dom5版本
npm i react-router-dom@5
react-router-dom相关API
内置组件
<BrowserRouter>
<HashRouter>
<Route>
<Redirect>
<Link>
<NavLink>
<Switch>
//具体使用
import { BrowserRouter, Route, Link, NavLink, Switch, Redirect } from 'react-router-dom';
export default class App extends React.Component {
render() {
return <BrowserRouter><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">
<NavLink className="list-group-item" to="/about">About</NavLink>
<NavLink className="list-group-item" to="/home">Home</NavLink>
</div>
</div>
<div className="col-xs-6">
<Switch>
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
<Redirect to="/home" />
</Switch>
</div>
</div>
</div>
</BrowserRouter>;
}
}
withRouter函数可以把一个非路由组件转换成路由组件
import { withRouter } from 'react-router-dom';
import Redux from './components/Redux/Redux';
class App extends React.Component {
render() {
return <div className='container'>
<Redux />
</div> ;
}
}
export default withRouter(App);
切换版本
- git reset --hard 『id 版本号』
- 如果忘记了,或者找不到版本号了, 可以使用 『git reflog』查看版本操作的历史
路由组件与一般组件的区别
-
使用方法不同
- 路由组件:
- 一般组件:
-
存放位置不同
- 路由组件: src/pages 文件夹 pages 页面
- 一般组件: src/components 文件夹
-
props 属性值不同
- 路由组件: {history, match, location}
- 一般组件: {a: '100', b: '200'}
params传参
1. 路由设置 `<Route path="/video" component={VideoDetail} />`
2.列表组件 <Link to={ "/video/"+item.id}> </Link>
3. VideoDetail组件 let id = this.props.location.pathname.split('/').pop();
params传参的简便写法
- 路由设置
<Route path="/video/:id" component={VideoDetail} /> - VideoDetail组件
let id = this.props.match.params.id;
query传参的流程
-
列表组件 <Link to={"/video?id=" + item.id}>
-
详情组件
let search = this.props.location.search.slice(1); // ?id=1 => {'?id' : 1} //在文件最上方需要引入qs模块 let res = qs.parse(search);// {id: 1} let id = res.id;
state 传参
-
列表组件
<Link to={{ pathname: '/video', //设置 URL 的路径 state: { id: item.id } //设置要传递的参数 }}> -
video组件
let id = this.props.location.state.id;
关于路由中的 URL
- 通过路由改变页面的 URL 不会发送请求
- 组件中发送 AJAX 请求的 URL 一定会发送请求
编程式路由导航
通过调用 JS 的方法, 实现路由的切换
//切换路由
check = (id) => {
return () => {
//调用方法 push 切换路由
//params
this.props.history.push('/video/' + id);
//query
// this.props.history.push('/video?id=' + id);
//state
// this.props.history.push({
// pathname: '/video',
// state: {
// id: id
// }
// })
//replace 方法切换路由 替换 用法与 push 完全相同
// this.props.history.replace({
// pathname: '/video',
// state: {
// id: id
// }
// })
}
}
//路由的前进与后退
前进:this.props.history.go(1);
后退:this.props.history.go(-1);
两种路由器组件的区别
-
URL 的形式不同
- BrowserRouter /video/2 更友好
- HashRouter #/video/2 弱
-
兼容性不同
- BrowserRouter 相对较弱 IE9
- HashRouter 相对较好 IE8
-
部署复杂性
- BrowserRouter 较为复杂 URL 重写
- HashRouter 较为简单
redux最终版
-
在src目录下创建redux文件夹
- actions文件夹:封装创建 action 对象的函数的js文件
- reducers文件夹:reducer 函数 『加工厂』的js文件
- constants.js文件:声明一些常量
- store.js文件:主文件
store.js中代码如下:
npm i redux
npm i redux-devtools-extension //安装redux开发工具
npm i redux-thunk //安装redux-chunk使store.dispatch()可以接收一个函数作为参数
import { composeWithDevTools } from 'redux-devtools-extension';
导入『创建状态仓库』的函数 createStore create 创建 store 仓库
import {createStore, applyMiddleware, combineReducers} from 'redux';
import thunk from 'redux-thunk';
import CollReducer from './reducers/CollReducer';
import CommentReducer from './reducers/CommentReducer';
//合并 reducer 函数
let reducer = combineReducers({
coll: CollReducer,
comment: CommentReducer
});
//4. 调用函数, 创建状态仓库 action 是一个对象, 两个属性: type 操作类型 data 操作使用的数据
const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)));
//异步修改状态
// store.dispatch((dispatch) => {
// setTimeout(() => {
// dispatch({type: 'COLL_ADD', data: 5})
// }, 2000)
// })
//6. 暴露 store 对象
export default store;
reducers文件夹下的CollReducer.js
//导入常量
import {COLL_ADD, COLL_MINUS} from '../constants';
//声明一个函数
const CollReducer = (state=100, action) => { // reducer 函数 『加工厂』
//根据操作的类型, 对 state 状态进行操作
switch(action.type){
//加法
case COLL_ADD:
//返回值, 将作为新的状态值. 更新状态
return state + action.data;
//减法
case COLL_MINUS:
return state - action.data;
//其他情况
default:
//这里一定要返回 state. 初始化的时候, 会执行该函数, 得到状态的初始值
return state;
}
}
export default CollReducer;
reducers文件夹下的CommentReducer.js
import {COMMENT_ADD, COMMENT_MINUS} from '../constants'
//声明一个函数
const CommentReducer = (state=10, action) => { // reducer 函数 『加工厂』
//根据操作的类型, 对 state 状态进行操作
switch(action.type){
//加法
case COMMENT_ADD:
//返回值, 将作为新的状态值. 更新状态
return state + action.data;
//减法
case COMMENT_MINUS:
return state - action.data;
//其他情况
default:
//这里一定要返回 state. 初始化的时候, 会执行该函数, 得到状态的初始值
return state;
}
}
export default CommentReducer;
actions文件夹下的CollAction.js
import {COLL_ADD, COLL_MINUS} from '../constants'
//5-3 封装创建 action 对象的函数
export function addAction(data){
return {
type: COLL_ADD,
data
}
}
export function minusAction(data){
return {
type: COLL_MINUS,
data
}
}
//5-4 异步修改状态
export function asyncAddAction(data){
return dispatch => {
//定时器
setTimeout(() => {
dispatch(addAction(data));
}, 1000)
}
}
actions文件夹下的CommentAction.js
//封装暴露函数, 作用: 返回 action 对象
import {COMMENT_ADD, COMMENT_MINUS} from '../constants';
export function jiaAction(data){
return {
type: COMMENT_ADD,
data: data
}
}
export function jianAction(data){
return {
type: COMMENT_MINUS,
data: data
}
}
export function asyncJiaAction(data){
return dispatch => {
setTimeout(() => {
dispatch(jiaAction(data));
}, 1000)
}
}
// store.dispatch({type: 'jia', data: 1});
// store.dispatch(jiaAction(1));
constants.js文件:
// constant 固定的 常量的
//收藏数
export const COLL_ADD = 'COLL_ADD';
export const COLL_MINUS = 'COLL_MINUS';
//评论数
export const COMMENT_ADD = 'COMMENT_ADD';
export const COMMENT_MINUS = 'COMMENT_MINUS';
在组件中的使用:
import React, { Component } from "react";
import store from "../../redux/store";
import { addAction, minusAction, asyncAddAction } from '../../redux/actions/CollAction';
import { jiaAction, jianAction, asyncJiaAction } from "../../redux/actions/CommentAction";
export default class Redux extends Component {
render() {
return (
<div>
<br />
<br />
<br />
<br />
<h2>Redux 状态操作</h2>
<hr />
<h4>收藏数 {store.getState().coll}</h4>
<button className="btn btn-danger btn-sm" onClick={this.minus}>
减少
</button>
<button className="btn btn-primary btn-sm" onClick={this.add}>
新增
</button>
<button className="btn btn-info btn-sm" onClick={this.asyncAdd}>
1s 后新增 2
</button>
<h4>评论数 {store.getState().comment}</h4>
<button onClick={() => {
store.dispatch(jianAction(1));
}}>减少</button>
<button onClick={() => {
store.dispatch(jiaAction(1));
}}>增加</button>
<button onClick={() => {
store.dispatch(asyncJiaAction(1));
}}>1s 后增加评论数</button>
</div>
);
}
add = () => {
//新增状态值
store.dispatch(addAction(1));
};
minus = () => {
//减少状态值
store.dispatch(minusAction(1));
}
//异步修改状态
asyncAdd = () => {
store.dispatch(asyncAddAction(10));
}
componentDidMount(){
console.log(store.getState());
}
}
React函数式组件
import React from 'react'
import axios from 'axios';
export default function Func(props) {
//props属性
console.log(props);
//创建一个状态,名为:night,值为:true
//setNight 函数, 用来修改状态的
let [night, setNight] = React.useState(true);
let [duanzi, setDuanzi] = React.useState([]);
//点击事件的回调
let handleClick = () => {
//修改状态
setNight(!night);
}
// ref 属性
const h4 = React.useRef();
//事件回调
let setHtml = () => {
// console.log(h4);
h4.current.innerHTML = '哈哈'
}
//获取段子列表的回调
let getDuanzi = async () => {
let { data } = await axios.get('http://api.xiaohigh.com/duanzi?_limit=10');
setDuanzi(data);
}
//模拟生命周期钩子 相当于是 componentDidMount 与 componentDidUpdate 的合体
React.useEffect(() => {
//钩子输出
console.log('我执行啦');
//声明异步函数
async function fn() {
let result = await axios.get('http://api.xiaohigh.com/duanzi?_limit=3');
setDuanzi(result.data);
}
//调用函数
fn();
//模拟 componentWillUnmount
//return 后面的函数类似于componentWillUnmount
return () => {
console.log('我卸载了!!');
}
}, [night]) // [] 设置的是, 哪些状态修改之后, 会再次执行回调
return (
<div style={{ padding: '50px', height: '2000px' }}>
<h2>状态 - state</h2>
<h4>至尊宝, 我是 {night ? '青霞' : '紫霞'}</h4>
<button onClick={handleClick}>切换白天与晚上</button>
<br /><br /><br />
<h2>属性 - props</h2>
<h4>{props.msg}</h4>
<br /><br /><br />
<h2>引用 - ref</h2>
<h4 ref={h4}></h4>
<button onClick={setHtml}>点击修改 H4 的文本内容</button>
<br /><br /><br />
<h3>发送AJAX请求</h3>
<button onClick={getDuanzi}>获取段子</button>
<ul>
{duanzi.map(item => {
return <li key={item.id}>{item.name}</li>
})}
</ul>
<br /><br /><br />
</div>
)
}
\