react是什么

用于构建用户界面的 JavaScript 库( 组件化开发 ) React 是一个采用声明式,高效而且灵活的用来构建用户界面的框架
ps: 用户界面 - User Interface - UI的简称

react特性

声明式

React 可以非常轻松地创建用户交互界面。为你应用的每一个状态设计简洁的视图,在数据改变时 React 也可以高效地更新渲染界面。
以声明式(jsx)编写UI,可以让你的代码更加可靠,且方便调试

组件化

react 组件封装了html代码,取代了 javascript template模板,通过扩展html元素 整合你的代码
创建好拥有各自状态的组件,再由组件构成更加复杂的用户界面。
无需再用模版代码,通过使用JavaScript编写的组件你可以更好地传递数据,将应用状态和DOM拆分开来。

一次学习,随处编写

轻松整合已经掌握的技术

安装

  1. npm install react --save
  2. npm install react-dom --save
  3. npm install react-router-dom --save

自动构建工具的集成

npm install create-react-app -g
npx create-react-app

启动脚手架创建的react 项目

npm run start
npm run eject

起步

CDN

内容分发网络,解决网络拥挤提升网络效率。

jsx

概念

jsx是 javascript and xml 的简写
xml 指可扩展标记语言,被设计用来传输和存储数据
json 写法比xml简单,类似与js的对象,以键值对的形式保存

特性

  • 设计jsx时被优化过了,性能较好
  • 类型安全,在编译过程就可以发现错误
  • 语法比较简洁,可以在js中写html

    意义

    我们之前创建一个template模板 而且想在js中写html 需要通过字符串(“”)引起来,插入变量的时候需要截断,非常麻烦,而且很容易出错, jsx 应用而生。
    1. //es5
    2. var result = "hello template";
    3. var temp = "<div>"+
    4. "<h1>"+result+"</h1>"+
    5. "</div>";
    6. //
    7. var result = ["吃饭饭","睡觉觉","打豆豆"];
    8. var temp = "<div>"+
    9. "<ul>"+
    10. result.forEach(item=>{
    11. "<li>这是:"+item+"</li>"
    12. })
    13. "</ul>"+
    14. "</div>";
    15. //es6 字符串模板
    16. var result = "hello template";
    17. var temp = `<div>
    18. <h1>${result}</h1>
    19. </div>`

    基本语法

    1. //这种看起来可能有些奇怪的标签语法既不是字符串也不是 HTML。
    2. const element = <h1>Hello, world!</h1>;

它被称为 JSX, 一种 JavaScript 的语法扩展

表达式

  1. let result = "hello template"
  2. let temp = <div>
  3. <h1>{result}</h1>
  4. </div>


使用方式

想让你的webpack项目认识jsx,需要:

  • babel-loader

webpack.config.js

  1. module.exports= {
  2. module: {
  3. rules: [{
  4. test: /\.(js|jsx)$/,
  5. loader: "babel-loader"
  6. }
  7. ]
  8. },
  9. }


  • react 支持 jsx (选择一个)

//手动搭建 babel 6.0
npm install —save-dev babel-preset-react
//脚手架 babel 7.0
npm install —save-dev babel-preset-react-app

  1. {
  2. "presets": [
  3. "react",
  4. "react-app"
  5. ]
  6. }

实现原理

  1. import React from "react"
  2. import ReactDOM from "react-dom"
  3. ReactDOM.render(
  4. <div></div>,
  5. document.getElementById("#app")
  6. )


传统操作dom绑定数据的方式及调优

  1. 1 document.createElement()
  2. 2 innerHTML
  1. // 通过appendChild 插入节点,来达到更新dom的目的
  2. const root = document.getElementById("root");
  3. const oHeader = document.createElement("header");
  4. const oH1 = document.createElmment("h1");
  5. oH1.innerHTML = "世界你好";
  6. oHeader.className="memeda";
  7. oHeader.appendChild(oH1);
  8. root.appendChild(oHeader)
  9. //通过innerHTML来替换目标节点的内容
  10. const root = document.getElementById("root");
  11. const temp = `<header>世界你好</header>`
  12. root.innerHTML = temp;


调优思路

  1. 通过createElement()创建dom需要频繁的插入节点,导致页面进行大量重绘,性能很差
  2. 我们可以通过 document.documentCreateFragement() 片段来完成dom 操作一次性插入到节点当中
  1. var d=document.createDocumentFragment();
  2. const oHeader = document.createElement("header");
  3. const oH1 = document.createElement("h1");
  4. oH1.innerHTML = "世界你好";
  5. oHeader.appendChild(oH1);
  6. d.appendChild(oHeader)
  7. document.getElementsByTagName("UL")[0].appendChild(d);


模拟jsx

  1. //创建好的对象就是一个虚拟dom
  2. createElement(tag, attrs, ...children) {
  3. //模拟dom树,返回一个js对象
  4. return {
  5. tag,
  6. attrs,
  7. children
  8. }
  9. }


虚拟dom

  • 用途 通过创建虚拟dom与原dom树的比较,插入更新模板
  • 算法 diff算法

    模拟

    1. const render = (vnode, container) => {
    2. //判断节点类型
    3. if (typeof vnode === "string") {
    4. const textNode = document.createTextNode(vnode)
    5. return container.appendChild(textNode)
    6. }
    7. //创建虚拟dom节点
    8. const virtualDom = document.createElement(vnode.tag);
    9. if (vnode.attrs) {
    10. Object.keys(vnode.attrs).forEach(key => {
    11. if (key === "className") {
    12. virtualDom.setAttribute("class", vnode.attrs[key])
    13. } else {
    14. virtualDom.setAttribute(key, vnode.attrs[key])
    15. }
    16. })
    17. }
    18. //遍历子元素,递归调用,并绑定元素
    19. vnode.children.forEach(child => {
    20. return render(child, virtualDom)
    21. })
    22. 将生成的虚拟dom树渲染
    23. return container.appendChild(virtualDom)
    24. }


    总结

  • 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)

  1. <a name="207ae2a3"></a>
  2. ## 函数定义组件
  3. 函数定义组件又被称为无状态组件,**下文一律称-无状态组件**<br />该函数是一个有效的React组件,它接收一个单一的“props”对象并返回了一个React元素。我们之所以称这种类型的组件为函数定义组件,是因为从字面上来看,它就是一个JavaScript函数。<br />定义组件时,render 返回的jsx模板如果有多个子节点,那么给组件的根节点元素最好用小括号包起来
  4. ```javascript
  5. const ProductList = (props)=>{
  6. return (<div>
  7. this is productList
  8. <p></p>
  9. </div>)
  10. }


es5创建组件

  1. var ProductList = React.createClass({
  2. render:function(){
  3. return <div>
  4. this is productList
  5. </div>
  6. }
  7. })

注意事项

  • 无状态组件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

} } class Parent extends React.Component{ render(){ const { datalist } = []; return
} }

  1. <br />
  2. <a name="vclS4"></a>
  3. ## 子父通讯
  4. 父级组件通过props给子组件传递一个回调函数,子级组件调用父级传递过来的回调,将参数返回
  5. ```javascript
  6. import React from "react";
  7. class Children extends React.Component{
  8. componentDidMount(){
  9. const { getData } = this.props;
  10. getData([1,2,3,4,5])
  11. }
  12. render(){
  13. return <div>
  14. this is children
  15. </div>
  16. }
  17. }
  18. class Parent extends React.Component{
  19. getData(val){
  20. //[1,2,3,4,5]
  21. console.log(val)
  22. }
  23. render(){
  24. return <div>
  25. <Children getData={this.getData}></Children>
  26. </div>
  27. }
  28. }


同级通讯

  1. npm install --save events
  2. const EventEmitter = require('events')
  3. const EventBus = new EventEmitter()
  4. //事件订阅
  5. EventBus.on("message", function (text) {
  6. console.log(text) //hello world
  7. })
  8. //事件发布
  9. EventBus.emit("message", 'hello world')

跨级组件通讯 - 发布订阅模式

  1. const eventProxy = {
  2. // onObj 存放多个监听事件的对象
  3. // oneObj 存放多个监听事件的对象,获取一次清空
  4. onObj: {},
  5. oneObj: {},
  6. //监听事件
  7. $on: function(key, fn) {
  8. // 当前请求事件在对象中是否存在
  9. //不存在 返回一个[]
  10. if (this.onObj[key] === undefined) {
  11. this.onObj[key] = [];
  12. }
  13. //将事件处理函数添加到对应的key
  14. this.onObj[key].push(fn);
  15. },
  16. $once: function(key, fn) {
  17. if (this.oneObj[key] === undefined) {
  18. this.oneObj[key] = [];
  19. }
  20. this.oneObj[key].push(fn);
  21. },
  22. // 移除事件监听
  23. $remove: function(key) {
  24. this.onObj[key] = [];
  25. this.oneObj[key] = [];
  26. },
  27. // 触发器或者发射器
  28. $emit: function() {
  29. let key, args;
  30. if (arguments.length == 0) {
  31. return false;
  32. }
  33. //trigger("update",data1,data2,data3)
  34. // 获取trigger函数的arguments,得到类数组
  35. // key 获取传参序列的第一项
  36. key = arguments[0]; //onObj[arguments[0]
  37. //类数组没有数组的slice,通过call改变this指向,让类数组继承数组的slice方法,完成截取功能
  38. args = [].concat(Array.prototype.slice.call(arguments, 1)); //data1,data2,data3
  39. // console.log(this.onObj[key][0]())
  40. if (this.onObj[key] !== undefined &&
  41. this.onObj[key].length > 0) {
  42. for (let i in this.onObj[key]) {
  43. // console.log(args)
  44. this.onObj[key][i].call(null, args);
  45. }
  46. }
  47. if (this.oneObj[key] !== undefined &&
  48. this.oneObj[key].length > 0) {
  49. for (let i in this.oneObj[key]) {
  50. // null 继承 this.oneObj[key][i]函数并调用,参数是args
  51. this.oneObj[key][i].apply(null, args);
  52. // console.log(args)
  53. this.oneObj[key][i] = undefined;
  54. }
  55. this.oneObj[key] = [];
  56. }
  57. }
  58. };
  59. eventProxy.$on("update", function(val) {
  60. console.log(val)
  61. })
  62. eventProxy.$once("update", function(val) {
  63. console.log(val)
  64. })
  65. eventProxy.$emit("update", 1, 2, 3)


跨级通讯

案例描述:
当前有三个组件,包裹顺序依次是: Parent > Middle > Children 现在 Parent组件有数据要传递给 Children组件

  • Parent > Middle > Children 具体方案请参考 props 传参
  • Parent > Children 具体方案如下,通context对象完成数据传递:

    1. import React from "react";
    2. import PropTypes from "prop-types";
    3. // 子级
    4. class Children extends React.Component{
    5. static contextTypes = {
    6. propA: PropTypes.string
    7. methodA: PropTypes.func
    8. }
    9. render(){
    10. return <div>
    11. this is children: {this.context.propA}
    12. </div>
    13. }
    14. }
    15. // 中间
    16. class Middle extends React.Component {
    17. render () {
    18. return <Children />
    19. }
    20. }
    21. // 父级
    22. class Parent extends React.Component{
    23. // 声明Context对象属性
    24. static childContextTypes = {
    25. propA: PropTypes.string,
    26. methodA: PropTypes.func
    27. }
    28. // 返回Context对象,方法名是约定好的
    29. getChildContext () {
    30. return {
    31. propA: 'propA',
    32. methodA: () => 'methodA'
    33. }
    34. }
    35. render(){
    36. return <div>
    37. <Middle/>
    38. </div>
    39. }
    40. }


    跨级组件通讯(新)

    1. conts { Provider ,Consumer}= React.createContect([defaultValue])
    2. class Child extends React.Component {
    3. render() {
    4. //Consumer只能通过一个回调函数返回一react元素
    5. return <Consumer>
    6. {context => <div>
    7. {
    8. context.map(item=>{
    9. return <li key={item.id}>
    10. {item.name}
    11. </li>
    12. })
    13. }
    14. </div>}
    15. </Consumer>
    16. }
    17. }
    18. class Middleware extends React.Component {
    19. render() {
    20. return <div>
    21. this is Middleware
    22. <Child/>
    23. </div>
    24. }
    25. }
    26. class App extends React.Component {
    27. constructor() {
    28. super()
    29. this.state = {
    30. productList: [{
    31. id: 1,
    32. name: "zhangsan"
    33. }, {
    34. id: 2,
    35. name: "lisi"
    36. }]
    37. }
    38. }
    39. render() {
    40. return <div className="wraper">
    41. <Header></Header>
    42. <Layout>
    43. //value 固定写法用来给子组件传参
    44. <Provider value={this.state.productList}>
    45. <Middleware/>
    46. </Provider>
    47. </Layout>
    48. </div>
    49. }
    50. }


    redux

    全局状态的管理库,详情请看redux指南

    操作dom

    dangerouslySetInnerHTML

    1. dangerouslySetInnerHTML
    2. // native
    3. var oDiv = document.getElementById("div");
    4. oDiv.innerHTML ="";
    5. // react
    6. const temp = { __html:"hahahaha"};
    7. <div dangerouslySetInnerHTML={temp} />;

findDOMNode

ref

//设置名字

//取值
this.refs.name

生命周期

无状态组件没有生命周期钩子函数

实例期

  1. constructor(){}

存在期

  1. componentWillMount() {} 组件将要加载
  2. componentDidMount() {} 组件加载完成
  3. componentWillUpdate() {} 物件将要更新
  4. componentDidUpdate() {} 组件完成更新
  5. shouldComponentUpdate(){} 组件可以更新?
  6. componentWillReceiveProps{} 组件将要接受props参数
  7. render(){}

销毁期

  1. componentWillUnmount() {} 销毁组件

组件初始化生命周期的执行过程

  • constructor
  • componentWillMount
  • render
  • componentDidMount

    组件内部状态(state)被改变生命周期的执行过程

  • shouldComponentUpdate() {}

  • componentWillUpdate() {}
  • render() {}
  • componentWillUpdate() {}

    组件外部部状态(props)被改变生命周期的执行过程

  • componentWillReceiveProps() {}

  • shouldComponentUpdate() {}
  • componentWillUpdate() {}
  • render() {}
  • componentWillUpdate() {}

    路由

    安装

    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
  • this.props.history

    动态路由配置

    1. // 动态路由组件
    2. import React from "react";
    3. import Routes from "./routes";
    4. import RouterMap from "./map";
    5. class RouterView extends React.Component {
    6. render() {
    7. const routes = this.props.routes ? this.props.routes : Routes;
    8. return <RouterMap routes={routes}/>
    9. }
    10. }
    11. export default RouterView;
    12. // 动态生成Routes及多级路由
    13. import React from "react";
    14. import { Switch, Route, Redirect } from "react-router-dom";
    15. class RouterMap extends React.Component {
    16. render() {
    17. const { routes } = this.props;
    18. const defaultRoute = <Route path="/" render={()=>{
    19. return <Redirect to="/index/rank"/>
    20. }} key={0} exact/>;
    21. return <Switch>
    22. {
    23. routes.length && routes.map((item,index)=>{
    24. const TempComponent = item.component;
    25. if(TempComponent){
    26. return <Route key={item.name} path={item.path} render={(config)=>{
    27. const children = item.children===undefined?[]:item.children;
    28. if(children.length){
    29. }
    30. return (children.length
    31. ?<TempComponent routes={children} {...config}/>
    32. :<TempComponent {...config}/>)
    33. }}/>
    34. }
    35. return true;
    36. }).concat([defaultRoute])
    37. }
    38. </Switch>
    39. }
    40. }
    41. export default RouterMap;
    42. // routes 配饰文件
    43. import Index from "view/index";
    44. import Rank from "view/index/rank";
    45. import RankDetail from "view/index/detail";
    46. import TopList from "view/index/toplist";
    47. import Search from "view/index/search";
    48. const Routes = [{
    49. path: "/index",
    50. name: "首页",
    51. component: Index,
    52. children: [{
    53. path: "/index/rank",
    54. name: "推荐",
    55. component: Rank
    56. },
    57. {
    58. path: "/index/rankDetail/:id",
    59. name: "详情",
    60. component: RankDetail
    61. }, {
    62. path: "/index/toplist",
    63. name: "排行",
    64. component: TopList
    65. }, {
    66. path: "/index/search",
    67. name: "搜索",
    68. component: Search
    69. }
    70. ]
    71. }]
    72. export default Routes;

传参

  • props.params

    1. //设置路由
    2. const routes = [{
    3. path:"/user",
    4. component:User
    5. },{
    6. path:"/user/:userId",
    7. component: Detail
    8. }]
    9. //跳转路由一:
    10. this.props.history.push({
    11. pathname:"/user/89757"
    12. })
    13. //跳转路由二:
    14. <Link to={{
    15. pathname:"/user/89757"
    16. }}>
    17. //取值
    18. this.props.match.params.userId


  • query 类似 get 请求

    1. //设置路由
    2. const routes = [{
    3. path:"/user",
    4. component:User
    5. },{
    6. path:"/user/detail",
    7. component: Detail
    8. }]
    9. //跳转路由一:
    10. this.props.history.push({
    11. pathname:"/user/detail",
    12. query:{
    13. userId:89757
    14. }
    15. })
    16. //跳转路由二:
    17. <Link to={{
    18. pathname:"/user/detail",
    19. query:{
    20. userId:89757
    21. }
    22. }}>
    23. //取值
    24. this.props.location.query.userId


  • state 类似post请求,使用方式与 query基本一致

    1. //设置路由
    2. const routes = [{
    3. path:"/user",
    4. component:User
    5. },{
    6. path:"/user/detail",
    7. component: Detail
    8. }]
    9. //跳转路由一:
    10. this.props.history.push({
    11. pathname:"/user/detail",
    12. state:{
    13. userId:89757
    14. }
    15. })
    16. //跳转路由二:
    17. <Link to={{
    18. pathname:"/user/detail",
    19. state:{
    20. userId:89757
    21. }
    22. }}>
    23. //取值
    24. this.props.location.state.userId


    props类型校验

    由于react在新版本中移除了类型校验,我们需要下载一个 prop-types 的包
    yarn add prop-types —save

  • PropTypes.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()
  1. import PropTypes from "prop-types";
  2. class App extends React.Component{
  3. //第一种写法
  4. static propTypes = {
  5. params: PropTypes.type
  6. }
  7. }
  8. //第二种写法
  9. App.propTypes= {
  10. params: PropTypes.type
  11. }


props 的默认值

写法一:
结合webpack+babel 6.0 需要添加 对与static修饰符的支持 (es6+不需要)
npm install —save-dev babel-preset-stage-0
.babelrc

  1. {
  2. "presets": ["stage-0"]
  3. }
  4. import PropTypes from "prop-types";
  5. class App extends React.Component{
  6. static defaultProps = {
  7. params: <any>
  8. }
  9. }
  10. 第二种写法
  11. import PropTypes from "prop-types";
  12. class App extends React.Component{
  13. }
  14. App.defaultProps= {
  15. params: <any>
  16. }