react是什么
用于构建用户界面的 JavaScript 库( 组件化开发 ) React 是一个采用声明式,高效而且灵活的用来构建用户界面的框架
ps: 用户界面 - User Interface - UI的简称
react特性
声明式
React 可以非常轻松地创建用户交互界面。为你应用的每一个状态设计简洁的视图,在数据改变时 React 也可以高效地更新渲染界面。
以声明式(jsx)编写UI,可以让你的代码更加可靠,且方便调试
组件化
react 组件封装了html代码,取代了 javascript template模板,通过扩展html元素 整合你的代码
创建好拥有各自状态的组件,再由组件构成更加复杂的用户界面。
无需再用模版代码,通过使用JavaScript编写的组件你可以更好地传递数据,将应用状态和DOM拆分开来。
一次学习,随处编写
安装
npm install react --savenpm install react-dom --savenpm install react-router-dom --save
自动构建工具的集成
npm install create-react-app -g
npx create-react-app
启动脚手架创建的react 项目
起步
CDN
jsx
概念
jsx是 javascript and xml 的简写
xml 指可扩展标记语言,被设计用来传输和存储数据
json 写法比xml简单,类似与js的对象,以键值对的形式保存
特性
- 设计jsx时被优化过了,性能较好
- 类型安全,在编译过程就可以发现错误
- 语法比较简洁,可以在js中写html
意义
我们之前创建一个template模板 而且想在js中写html 需要通过字符串(“”)引起来,插入变量的时候需要截断,非常麻烦,而且很容易出错, jsx 应用而生。//es5var result = "hello template";var temp = "<div>"+"<h1>"+result+"</h1>"+"</div>";//var result = ["吃饭饭","睡觉觉","打豆豆"];var temp = "<div>"+"<ul>"+result.forEach(item=>{"<li>这是:"+item+"</li>"})"</ul>"+"</div>";//es6 字符串模板var result = "hello template";var temp = `<div><h1>${result}</h1></div>`
基本语法
//这种看起来可能有些奇怪的标签语法既不是字符串也不是 HTML。const element = <h1>Hello, world!</h1>;
表达式
let result = "hello template"let temp = <div><h1>{result}</h1></div>
使用方式
想让你的webpack项目认识jsx,需要:
- babel-loader
webpack.config.js
module.exports= {module: {rules: [{test: /\.(js|jsx)$/,loader: "babel-loader"}]},}
- react 支持 jsx (选择一个)
//手动搭建 babel 6.0
npm install —save-dev babel-preset-react
//脚手架 babel 7.0
npm install —save-dev babel-preset-react-app
{"presets": ["react","react-app"]}
实现原理
import React from "react"import ReactDOM from "react-dom"ReactDOM.render(<div></div>,document.getElementById("#app"))
传统操作dom绑定数据的方式及调优
1 document.createElement()2 innerHTML
// 通过appendChild 插入节点,来达到更新dom的目的const root = document.getElementById("root");const oHeader = document.createElement("header");const oH1 = document.createElmment("h1");oH1.innerHTML = "世界你好";oHeader.className="memeda";oHeader.appendChild(oH1);root.appendChild(oHeader)//通过innerHTML来替换目标节点的内容const root = document.getElementById("root");const temp = `<header>世界你好</header>`root.innerHTML = temp;
调优思路
通过createElement()创建dom需要频繁的插入节点,导致页面进行大量重绘,性能很差我们可以通过 document.documentCreateFragement() 片段来完成dom 操作一次性插入到节点当中
var d=document.createDocumentFragment();const oHeader = document.createElement("header");const oH1 = document.createElement("h1");oH1.innerHTML = "世界你好";oHeader.appendChild(oH1);d.appendChild(oHeader)document.getElementsByTagName("UL")[0].appendChild(d);
模拟jsx
//创建好的对象就是一个虚拟domcreateElement(tag, attrs, ...children) {//模拟dom树,返回一个js对象return {tag,attrs,children}}
虚拟dom
- 用途 通过创建虚拟dom与原dom树的比较,插入更新模板
-
模拟
const render = (vnode, container) => {//判断节点类型if (typeof vnode === "string") {const textNode = document.createTextNode(vnode)return container.appendChild(textNode)}//创建虚拟dom节点const virtualDom = document.createElement(vnode.tag);if (vnode.attrs) {Object.keys(vnode.attrs).forEach(key => {if (key === "className") {virtualDom.setAttribute("class", vnode.attrs[key])} else {virtualDom.setAttribute(key, vnode.attrs[key])}})}//遍历子元素,递归调用,并绑定元素vnode.children.forEach(child => {return render(child, virtualDom)})将生成的虚拟dom树渲染return container.appendChild(virtualDom)}
总结
react 综合渲染性能较强 结合了jsx以及dif算法,通过创建虚拟dom与原dom树的比较,替换需要更新的模板,完成数据绑定
误区 一般情况下,原生js实现渲染性能好,从普适性和整体性能来看,react做的diff算法在前端框架中较好
组件
组件可以将UI切分成一些独立的、可复用的部件,这样你就只需专注于构建每一个单独的部件。
组件从概念上看就像是函数,它可以接收任意的输入值(称之为“props”),并返回一个需要在页面上展示的React元素
react 组件的名称,首字母一律大写,并且使用驼峰写法类定义组件
如果没有组件私有状态(state)需要去定义,根据es6 class的原则,可以省略不写constructor ```javascript import React from “react”; class App extends React.Component{
constructor(props){ super(props); } //通过render方法渲染组件的模板 render(){ return
} }
// 解构了 Component,依然需要引入 React, 否则报错 import React, { Component } from “react”; class App extends Component{ //通过render方法渲染组件的模板,并且render只能返回一个根节点元素 render(){ return
} } import React from “react”; import ReactDOM from “react-dom”;const template =
; const element = document.getElementById(“#app”); ReactDOM.render(template,element)
<a name="207ae2a3"></a>## 函数定义组件函数定义组件又被称为无状态组件,**下文一律称-无状态组件**<br />该函数是一个有效的React组件,它接收一个单一的“props”对象并返回了一个React元素。我们之所以称这种类型的组件为函数定义组件,是因为从字面上来看,它就是一个JavaScript函数。<br />定义组件时,render 返回的jsx模板如果有多个子节点,那么给组件的根节点元素最好用小括号包起来```javascriptconst ProductList = (props)=>{return (<div>this is productList<p></p></div>)}
es5创建组件
var ProductList = React.createClass({render:function(){return <div>this is productList</div>}})
注意事项
- 无状态组件prop不要通过this.props来调用
- 无状态组件没有生命周期
- 无状态组件不能定义定义状态(state)
- 无状态组件不能实例化,组件名不能被 new
- 无状态组件只能返回一个根节点元素
- 类组件render函数,只能返回一个根节点元素
- 类组件如果显式的声明了 constructor, 必须调用 super()
组件通讯
父子通讯
父级组件调用子组件,通过props来传递参数,子组件通过this.props来接收 ```javascript import React from “react”;
class Children extends React.Component{ render(){ const { datalist } = this.props; return
<br /><a name="vclS4"></a>## 子父通讯父级组件通过props给子组件传递一个回调函数,子级组件调用父级传递过来的回调,将参数返回```javascriptimport React from "react";class Children extends React.Component{componentDidMount(){const { getData } = this.props;getData([1,2,3,4,5])}render(){return <div>this is children</div>}}class Parent extends React.Component{getData(val){//[1,2,3,4,5]console.log(val)}render(){return <div><Children getData={this.getData}></Children></div>}}
同级通讯
npm install --save eventsconst EventEmitter = require('events')const EventBus = new EventEmitter()//事件订阅EventBus.on("message", function (text) {console.log(text) //hello world})//事件发布EventBus.emit("message", 'hello world')
跨级组件通讯 - 发布订阅模式
const eventProxy = {// onObj 存放多个监听事件的对象// oneObj 存放多个监听事件的对象,获取一次清空onObj: {},oneObj: {},//监听事件$on: function(key, fn) {// 当前请求事件在对象中是否存在//不存在 返回一个[]if (this.onObj[key] === undefined) {this.onObj[key] = [];}//将事件处理函数添加到对应的keythis.onObj[key].push(fn);},$once: function(key, fn) {if (this.oneObj[key] === undefined) {this.oneObj[key] = [];}this.oneObj[key].push(fn);},// 移除事件监听$remove: function(key) {this.onObj[key] = [];this.oneObj[key] = [];},// 触发器或者发射器$emit: function() {let key, args;if (arguments.length == 0) {return false;}//trigger("update",data1,data2,data3)// 获取trigger函数的arguments,得到类数组// key 获取传参序列的第一项key = arguments[0]; //onObj[arguments[0]//类数组没有数组的slice,通过call改变this指向,让类数组继承数组的slice方法,完成截取功能args = [].concat(Array.prototype.slice.call(arguments, 1)); //data1,data2,data3// console.log(this.onObj[key][0]())if (this.onObj[key] !== undefined &&this.onObj[key].length > 0) {for (let i in this.onObj[key]) {// console.log(args)this.onObj[key][i].call(null, args);}}if (this.oneObj[key] !== undefined &&this.oneObj[key].length > 0) {for (let i in this.oneObj[key]) {// null 继承 this.oneObj[key][i]函数并调用,参数是argsthis.oneObj[key][i].apply(null, args);// console.log(args)this.oneObj[key][i] = undefined;}this.oneObj[key] = [];}}};eventProxy.$on("update", function(val) {console.log(val)})eventProxy.$once("update", function(val) {console.log(val)})eventProxy.$emit("update", 1, 2, 3)
跨级通讯
案例描述:
当前有三个组件,包裹顺序依次是: Parent > Middle > Children 现在 Parent组件有数据要传递给 Children组件
- Parent > Middle > Children 具体方案请参考 props 传参
Parent > Children 具体方案如下,通context对象完成数据传递:
import React from "react";import PropTypes from "prop-types";// 子级class Children extends React.Component{static contextTypes = {propA: PropTypes.stringmethodA: PropTypes.func}render(){return <div>this is children: {this.context.propA}</div>}}// 中间class Middle extends React.Component {render () {return <Children />}}// 父级class Parent extends React.Component{// 声明Context对象属性static childContextTypes = {propA: PropTypes.string,methodA: PropTypes.func}// 返回Context对象,方法名是约定好的getChildContext () {return {propA: 'propA',methodA: () => 'methodA'}}render(){return <div><Middle/></div>}}
跨级组件通讯(新)
conts { Provider ,Consumer}= React.createContect([defaultValue])class Child extends React.Component {render() {//Consumer只能通过一个回调函数返回一react元素return <Consumer>{context => <div>{context.map(item=>{return <li key={item.id}>{item.name}</li>})}</div>}</Consumer>}}class Middleware extends React.Component {render() {return <div>this is Middleware<Child/></div>}}class App extends React.Component {constructor() {super()this.state = {productList: [{id: 1,name: "zhangsan"}, {id: 2,name: "lisi"}]}}render() {return <div className="wraper"><Header></Header><Layout>//value 固定写法用来给子组件传参<Provider value={this.state.productList}><Middleware/></Provider></Layout></div>}}
redux
全局状态的管理库,详情请看redux指南
操作dom
dangerouslySetInnerHTML
dangerouslySetInnerHTML// nativevar oDiv = document.getElementById("div");oDiv.innerHTML ="";// reactconst temp = { __html:"hahahaha"};<div dangerouslySetInnerHTML={temp} />;
findDOMNode
ref
生命周期
实例期
constructor(){}
存在期
componentWillMount() {} 组件将要加载componentDidMount() {} 组件加载完成componentWillUpdate() {} 物件将要更新componentDidUpdate() {} 组件完成更新shouldComponentUpdate(){} 组件可以更新?componentWillReceiveProps{} 组件将要接受props参数render(){}
销毁期
componentWillUnmount() {} 销毁组件
组件初始化生命周期的执行过程
- constructor
- componentWillMount
- render
-
组件内部状态(state)被改变生命周期的执行过程
shouldComponentUpdate() {}
- componentWillUpdate() {}
- render() {}
-
组件外部部状态(props)被改变生命周期的执行过程
componentWillReceiveProps() {}
- shouldComponentUpdate() {}
- componentWillUpdate() {}
- render() {}
-
路由
安装
npm install react-router-dom —save
分类
历史模式 BrowserRouter
- 哈希模式 HashRouter
注意(前方高能) 使用BrowserRouter时,我们需要与后端配合,因为访问路由会像一个真实的url路径,会被后端解析。
http://localhost:8080/login/heinan
我们通过react路由配置出这样的一个动态路由时,后端会根据上面地址去找对应的接口和页面,没有找到时,将会返回一个404的错误的页面
解决方案
- 使用webpack-dev-server 来mock 数据,我们需要开启
module.exports = {
devServer:{
historyApiFallback:true
}
}
后端添加前端路由的过滤白名单 思考: 当我们在浏览器,输入一个url地址时,发生了什么?
常用API
Route
- Switch
- Link
- NavLink
- this.props.match
- this.props.location
-
动态路由配置
// 动态路由组件import React from "react";import Routes from "./routes";import RouterMap from "./map";class RouterView extends React.Component {render() {const routes = this.props.routes ? this.props.routes : Routes;return <RouterMap routes={routes}/>}}export default RouterView;// 动态生成Routes及多级路由import React from "react";import { Switch, Route, Redirect } from "react-router-dom";class RouterMap extends React.Component {render() {const { routes } = this.props;const defaultRoute = <Route path="/" render={()=>{return <Redirect to="/index/rank"/>}} key={0} exact/>;return <Switch>{routes.length && routes.map((item,index)=>{const TempComponent = item.component;if(TempComponent){return <Route key={item.name} path={item.path} render={(config)=>{const children = item.children===undefined?[]:item.children;if(children.length){}return (children.length?<TempComponent routes={children} {...config}/>:<TempComponent {...config}/>)}}/>}return true;}).concat([defaultRoute])}</Switch>}}export default RouterMap;// routes 配饰文件import Index from "view/index";import Rank from "view/index/rank";import RankDetail from "view/index/detail";import TopList from "view/index/toplist";import Search from "view/index/search";const Routes = [{path: "/index",name: "首页",component: Index,children: [{path: "/index/rank",name: "推荐",component: Rank},{path: "/index/rankDetail/:id",name: "详情",component: RankDetail}, {path: "/index/toplist",name: "排行",component: TopList}, {path: "/index/search",name: "搜索",component: Search}]}]export default Routes;
传参
props.params
//设置路由const routes = [{path:"/user",component:User},{path:"/user/:userId",component: Detail}]//跳转路由一:this.props.history.push({pathname:"/user/89757"})//跳转路由二:<Link to={{pathname:"/user/89757"}}>//取值this.props.match.params.userId
query 类似 get 请求
//设置路由const routes = [{path:"/user",component:User},{path:"/user/detail",component: Detail}]//跳转路由一:this.props.history.push({pathname:"/user/detail",query:{userId:89757}})//跳转路由二:<Link to={{pathname:"/user/detail",query:{userId:89757}}}>//取值this.props.location.query.userId
state 类似post请求,使用方式与 query基本一致
//设置路由const routes = [{path:"/user",component:User},{path:"/user/detail",component: Detail}]//跳转路由一:this.props.history.push({pathname:"/user/detail",state:{userId:89757}})//跳转路由二:<Link to={{pathname:"/user/detail",state:{userId:89757}}}>//取值this.props.location.state.userId
props类型校验
由于react在新版本中移除了类型校验,我们需要下载一个 prop-types 的包
yarn add prop-types —savePropTypes.number
- PropTypes.string
- PropTypes.func
- PropTypes.bool
- PropTypes.object
- PropTypes.array
- PropTypes.symbol
- PropTypes.node 验证值为节点
- PropTypes.element 验证值为元素
- PropTypes.any 任意数据类型
- PropTypes.oneOfType([types…]) 满足数组中的一种验证规则就可以通过
- PropTypes.isRequired props必须传值,可以链式调用
- callback(props,propsName,componentName) 自定义校验,必须返回一个new Error()
import PropTypes from "prop-types";class App extends React.Component{//第一种写法static propTypes = {params: PropTypes.type}}//第二种写法App.propTypes= {params: PropTypes.type}
props 的默认值
写法一:
结合webpack+babel 6.0 需要添加 对与static修饰符的支持 (es6+不需要)
npm install —save-dev babel-preset-stage-0
.babelrc
{"presets": ["stage-0"]}import PropTypes from "prop-types";class App extends React.Component{static defaultProps = {params: <any>}}第二种写法import PropTypes from "prop-types";class App extends React.Component{}App.defaultProps= {params: <any>}
