react是什么
用于构建用户界面的 JavaScript 库( 组件化开发 ) React 是一个采用声明式,高效而且灵活的用来构建用户界面的框架
ps: 用户界面 - User Interface - UI的简称
react特性
声明式
React 可以非常轻松地创建用户交互界面。为你应用的每一个状态设计简洁的视图,在数据改变时 React 也可以高效地更新渲染界面。
以声明式(jsx)编写UI,可以让你的代码更加可靠,且方便调试
组件化
react 组件封装了html代码,取代了 javascript template模板,通过扩展html元素 整合你的代码
创建好拥有各自状态的组件,再由组件构成更加复杂的用户界面。
无需再用模版代码,通过使用JavaScript编写的组件你可以更好地传递数据,将应用状态和DOM拆分开来。
一次学习,随处编写
安装
npm install react --save
npm install react-dom --save
npm 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 应用而生。//es5
var 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
//创建好的对象就是一个虚拟dom
createElement(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模板如果有多个子节点,那么给组件的根节点元素最好用小括号包起来
```javascript
const 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给子组件传递一个回调函数,子级组件调用父级传递过来的回调,将参数返回
```javascript
import 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 events
const 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] = [];
}
//将事件处理函数添加到对应的key
this.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]函数并调用,参数是args
this.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.string
methodA: 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
// native
var oDiv = document.getElementById("div");
oDiv.innerHTML ="";
// react
const 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>
}