React笔记

524 阅读13分钟

第一部分:react

一、react开发环境准备

A. 通过脚手架工具来编码

  • Creact-react-app 官方提供的脚手架工具
  • node -v 查看node版本号
  • npm -v 查看nom版本号

B. 安装

  • npm i creat-react-app -g 在全局安装依赖
  • creat-react-app todolist 创建todolist文件
  • cd todolist 基于creat-react-app构建的todolist项目目录
  • npm start 启动开发服务器。
  • npm run build 将应用程序打包成静态文件进行生产。
  • npm test 启动测试运行程序。
  • npm start NPM启动

在浏览器输入http://localhost:3000/,出现react欢迎页即项目启动成功

二、项目目录分析

  • node-modules node包文件
  • public
    • favicon.icon url 图标
    • index.html 项目首页
    • manifest.json
  • src
    • index.js 整个程序运行的入口文件 引入App文件
    • App.js 页面内容
  • gitgnore 上传git时,如果有文件不想传到git上,可以放在这个文件下
  • package-lock.json 项目文件依赖
  • package.json 项目介绍 node包文件
  • README.md 项目文件说明

在index.js文件中有一行代码

// PWA 手机App
// https协议的服务器上 (用户第一次点击页面需要联网,第二次点击时如果断网仍可访问该页面)
import * as serviceWorker from './serviceWorker';

三、react中的组件

//App.js文件
//App组件即为react中一个简单的组件 render里面返回的内容即组件显示的内容
import React, { Component } from 'react';
class App extends Component {
  constructor(props) {
    super(props);
    this.state = {}
  }
  render() {
    return (
      <div>
        hello world
    </div>
    );
  }
}
// 导出组件
export default App;
//index.js组件文件
import React from 'react'; 
import ReactDOM from 'react-dom';
import App from './App'; // 引入App组件
// 第三方模块 把App组件挂载到root节点中 root节点就会展示组件的内容
ReactDOM.render(<App />, document.getElementById('root'));

创建组件的两种方式

1)用class创建组件

import React from 'react';
import ReactDOM from 'react-dom';
if(module.hot){
    module.hot.accept();
}
// 1) class创建组件
class App extends React.Component{
    render(){
        return (
            <div>
                <div>Hai Jess</div>
                <div>Hai Alex</div>
            </div>
        )
    }
}

ReactDOM.render(
    <App/>,
    document.getElementById('root')
)

2) 函数式创建组件

import React from 'react';
import ReactDOM from 'react-dom';
if(module.hot){
    module.hot.accept();
}

// 2) 函数式创建组件 
function App() {
    return (
        <div>
            <div>Hai Jess</div>
            <div>Hai Alex</div>
        </div>
    )
}

ReactDOM.render(
    <App/>,
    document.getElementById('root')
)

四、JSX语法

JSX语法:可扩展的js语法,不是字符串也不是html

    1. 结构顶层只能有一个元素
// 例如:
<div>hello world!</div><div>123</div>
//会报错
<div>
    <div>hello world!</div>
    <div>123</div>
</div>
//不会报错
    1. class必须写className
    1. 标签必须是闭合状态 <input type='text'/>
    1. { }
    • 可以执行JS表达式
    • 花括号内有数组默认展开
    • 如果元素属性是一个变量需要花括号 src={obj.xx}
    • 花括号中不能写for循环
    1. 受控组件与非受控组件
    • 如果在表单元素上设置一个默认值(value / checked),该元素为受控组件(即无法输入其他内容)
    • 解决方案:加default
      • <input type='text' defaultValue="hahaha"/>
      • <input type='checkbox' defaultChecked/>
    1. dangerouslySetInnerHTML 在JSX中希望显示一些内容,不希望被自动转义(即在input输入框中输入

      哈哈

      ,在页面中显示文字,不显示标签,即通过dangerouslySetInnerHTML属性来设置
    • dangerouslySetInnerHTML={{__html:item}}
     <ul>
        {
            // 渲染页面
            this.state.arr.map((item, i) => {
                return (
                    <li key={i} 
                        onClick={this.delItem.bind(this,i)}
                        dangerouslySetInnerHTML={{__html:item}}
                    ></li>
                )
            })
        }
    </ul>
    
    1. label标签
    // label内的for属性 要写成 htmlFor
    <label htmlFor="text">输入内容</label>
    <input
        id="text"
        className="input"
        type="text"
        onChange={this.inputChange.bind(this)} //为了让input框value值改变 必须要写onChange方法
        value={this.state.val}
    />
    

五、实现一个todolist页面

//Todolist.js组件文件
import React, { Component,Fragment } from 'react';
// 引入Fragment占位符

class Todolist extends Component {
    constructor(props) {
        super(props);
        this.state = {}
    }
    render() {
        return (
            // Fragment占位符会让最外层的结构不在页面中显示,即页面中会直接显示Fragment标签里面的结构内容
            <Fragment>
                <div>
                    <input />
                    <button>提交</button>
                </div>
                <ul>
                    <li>123</li>
                    <li>456</li>
                    <li>123</li>
                    <li>123</li>
                </ul>
            </Fragment>
        );
    }
}

export default Todolist;

六、react中的响应式设计和事件绑定

class Todolist extends Component {
    constructor(props) { //接收props参数
        super(props); //调用父类的构造函数
        this.state = { //组件的状态
            val: '',
            arr: ['Tom','Jess']
        }
    }
    //改变input框的value值
    inputChange (ev){
        console.log(ev.target.value);//input框中输入的value值
        this.setState({ //setState方法会改变state状态中的数据,从而使页面发生改变
            val: ev.target.value //让input中输入的值赋值给state状态的val
        })
    }
    //点击提交按钮
    btnClick(){
        // copy数组,把val值添加到新数组中
        let {arr,val} = this.state;
        arr.push(val)
        this.setState({arr,val:''})
    }
    //点击删除任务
    delItem(id){
        // immutable 
        // state 不允许做任何改变 
        console.log(id)
        const arr = [...this.state.arr];
        arr.splice(id,1);
        this.setState({arr})
    }
    render() {
        return (
            // Fragment占位符会让最外层的结构不在页面中显示,即页面中会直接显示Fragment标签里面的结构内容
            <Fragment>
                <div>
                    <input
                        type="text"
                        onChange={this.inputChange.bind(this)} //为了让inputvalue值改变 必须要写onChange方法
                        value={this.state.val}
                    />
                    <button
                        onClick={this.btnClick.bind(this)}
                    >提交</button>
                </div>
                <ul>
                    {
                        // 渲染页面
                        this.state.arr.map((item, i) => {
                            return (<li key={i} onClick={this.delItem.bind(this,i)}>{item}</li>)
                        })
                    }
                </ul>
            </Fragment>
        );
    }
}

七、组件之间的传值

在src文件夹下创建一个文件夹 components,即组件

父组件向子组件传递数据

  • 父组件上给子组件中绑定自定义属性,把数据放到自定义属性上
  • 子组件中使用 this.props.zdy 去接收
//父组件
class App extends Component {
    constructor() {
        super();
        this.state = {
            arr: [11, 22, 33, 44, 55]
        }
    }
  
    render() {
        let {arr} = this.state;
        let arr1 = arr.concat();
        let list = arr1.map((item,i)=>{
            return (
                <List 
                    {...{
                        text:item,
                        key:i,
                        a:123,
                        b:456,
                        c:789
                    }}
                />
            )
        })
        return (
            {/* <List data={this.state.arr}/> */}
            <ul>{list}</ul>
        )
    }
}

//子组件
class List extends Component {
    constructor(){
        super();
        this.state = {}
    }
    render(){
        let {text,key,a,b,c} = this.props;
        console.log(key);
        return(
            <li>{text}</li>
        )
    }
}

八、围绕react衍生的思考

react:

  • 声明式开发 => 数据驱动 减少大量操作DOM的代码量
  • 可以与其他框架共存
  • 组件式开发 =>
    • 普通标签与组件的区别:组件标签首字母大写
  • 单向数据流
  • 视图层框架:大型界面开发时,React.js 只负责视图层内容,我们还需要数据层框架(redux/Flux)等的支持。因为很明显,当复杂的组件关系之间,需要传递数据,React.js 会非常麻烦。
  • 函数式编程 => 易维护 容易进行前端自动化测试

数据的单向流动

react数据单向流:子父组件之间的通讯(数据传递)规则,原则:数据只能从父组件传递到子组件,而不能由子组件直接修改父组件的数据,数据属于谁谁才有资格去修改;

  • 情况一: 父级的数据传到子级,数据本身还是父级的,如果用户操作子级要改变传递的数据,那么不能子级改,要让父级修改 父级要定义一个修改数据的方法,在传递数据的时候也一起传给子级 当触发子级行为的时候,子级去调用修改父级数据的方法,然后父级收到后 子级的修改,父级修改数据,当父级的数据发生变化时,又把最新的数据传给子级

  • 情况二: 父级把数据给了子级,子级只想在触发子级的时候,子级的数据改变,父级数据不改变 也就是,父级通过自定义的方式传数据给子级,子级可以在constructor中接收到父级传递的数据(就一次),把父级传递的这个数据,变为 this.state,自己就拥有了父级的数据,并且修改自己的数据不会影响到父级

九、PropTypes与DeaultProps

子组件接收父组件的参数,可能是函数,数字类型,字符串类型,这时需要基于PropTypes对属性接收做一个强校验

  • 引入PropTypes:import PropTypes from 'prop-types';
  • defaultProps : 在子组件给这个必传值设置一个默认值

import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';

class TodoItem extends Component {
    constructor(props) {
        super(props);
        this.state = {}
    }
    //点击删除 子组件调用父组件
    delItem = () => {
        let { delItem, i } = this.props;
        delItem(i);
    }
    render() {
        let { content, test } = this.props;
        return (
            <Fragment>
                <li
                    onClick={this.delItem}
                >
                {test}-{content}
                </li>
            </Fragment>
        );
    }
}

//属性强校验 在子组件限制父组件传值的类型,如果父组件没有按照子组件设置的类型传值会报错 不会影响代码执行,但是有利于开发
TodoItem.propTypes = {
    test: PropTypes.string.isRequired, //表示test需要从父组件传递一个字符串类型的值,而且必须传值,如果父组件没有传值会报错
    content: PropTypes.string,
    delItem: PropTypes.func,
    i: PropTypes.number
};

// 如果父组件确实无法传这个值(test),则需要在子组件给这个必传值设置一个默认值
TodoItem.defaultProps = {
    test: 'hello eorld'
}
export default TodoItem;

关于propTypes在官网有更多的写法

import PropTypes from 'prop-types';

MyComponent.propTypes = {
  // 你可以将属性声明为 JS 原生类型,默认情况下
  // 这些属性都是可选的。
  optionalArray: PropTypes.array,
  optionalBool: PropTypes.bool,
  optionalFunc: PropTypes.func,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalString: PropTypes.string,
  optionalSymbol: PropTypes.symbol,

  // 任何可被渲染的元素(包括数字、字符串、元素或数组)
  // (或 Fragment) 也包含这些类型。
  optionalNode: PropTypes.node,

  // 一个 React 元素。
  optionalElement: PropTypes.element,

  // 你也可以声明 prop 为类的实例,这里使用
  // JS 的 instanceof 操作符。
  optionalMessage: PropTypes.instanceOf(Message),

  // 你可以让你的 prop 只能是特定的值,指定它为
  // 枚举类型。
  optionalEnum: PropTypes.oneOf(['News', 'Photos']),

  // 一个对象可以是几种类型中的任意一个类型
  optionalUnion: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.instanceOf(Message)
  ]),

  // 可以指定一个数组由某一类型的元素组成
  optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

  // 可以指定一个对象由某一类型的值组成
  optionalObjectOf: PropTypes.objectOf(PropTypes.number),

  // 可以指定一个对象由特定的类型值组成
  optionalObjectWithShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number
  }),

  // 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
  // 这个 prop 没有被提供时,会打印警告信息。
  requiredFunc: PropTypes.func.isRequired,

  // 任意类型的数据
  requiredAny: PropTypes.any.isRequired,

  // 你可以指定一个自定义验证器。它在验证失败时应返回一个 Error 对象。
  // 请不要使用 `console.warn` 或抛出异常,因为这在 `onOfType` 中不会起作用。
  customProp: function(props, propName, componentName) {
    if (!/matchme/.test(props[propName])) {
      return new Error(
        'Invalid prop `' + propName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  },

  // 你也可以提供一个自定义的 `arrayOf` 或 `objectOf` 验证器。
  // 它应该在验证失败时返回一个 Error 对象。
  // 验证器将验证数组或对象中的每个值。验证器的前两个参数
  // 第一个是数组或对象本身
  // 第二个是他们当前的键。
  customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
    if (!/matchme/.test(propValue[key])) {
      return new Error(
        'Invalid prop `' + propFullName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  })
};

十、props,state,render的关系

当组件的state或者props发生改变的时候,render函数就会重新执行 当父组件的render函数被运行时,它的子组件render都将被重新运行一次

十一、虚拟DOM

  • 1)state 数据
  • 2)JSX模板 => render函数下的结构
  • 3)数据 + 模板 结合,生成真实的DOM,来显示
  • 4)state发生改变
  • 5)数据 + 模板 结合,生成真实的DOM,替换原始DOM

缺陷: 第一次生成了一个完整的DOM片段, 第二次生成了一个完整的DOM片段, 第二次的片段替换了第一次的片段, 这样十分消耗性能

改进方案1:

  • 1)state 数据
  • 2)JSX模板 => render函数下的结构
  • 3)数据 + 模板 结合,生成真实的DOM,来显示
  • 4)state发生改变
  • 5)数据 + 模板 结合,生成真实的DOM,并不直接替换原始的DOM
  • 6)新的DOM(Document Fragment文档碎片)和原始的DOM作比对,找差异
  • 7)找出input框发生变化
  • 8)只用新的DOM中的input,替换原始DOM中的input元素

缺陷: 新的DOM和原始的DOM作比对的过程也会消耗性能,性能提升并不明显

改进方案2:虚拟DOM

  • 1)state 数据
  • 2)JSX模板 => render函数下的结构
  • 3)生成虚拟DOM(虚拟DOM就是一个真实的对象,用它来描述真实的DOM)
    • ['div',{id:'abc'},['span,{},'哈哈']
  • 4)用虚拟DOM的结构,生成真实的DOM,来显示
    • <div id="abc"><span>哈哈</span></div>
  • 5)state发生变化
  • 6)数据 + 模板 生成新的虚拟DOM (极大的提升性能)
    • ['div',{id:'abc'},['span,{},'bye bye']
  • 7)比较原始虚拟DOM和最新虚拟DOM的区别,此例即span中的内容(比对的是js对象,不是真实的DOM)
    • 比较的过程用到Diff算法(diffence)
  • 8)直接操作DOM,改变span中的内容

虚拟DOM优点:

  • 提升性能
  • 使得跨端应用得以实现(React Native)

十二、虚拟DOM中的diff算法

十三、ref

可以快速的获取组件或者元素 在指定组件上写一个ref的属性,任意值

<App ref="app"/> // 定义组件
this.ref.app // 获得这个组件

十四、react中的生命周期函数

生命周期函数,是指在某一个时刻组件会自动调用执行的函数

14.1 mouting阶段 (只执行一次)

  • constructor 初始化数据
  • componentWillMount 挂载之前
  • render 渲染 每次渲染时要处理的逻辑
  • componentDidMount 请求数据 获取到真实的DOM
    constructor(){
        // 初始化数据
        super();
        this.state = {}
        console.log(1)
    }
    componentWillMount(){
        console.log('挂载之前')
    }
    render(){
        // 第一次渲染
        console.log('渲染')
    }
    componentDidMount(){
        // 请求数据
        console.log('挂载之后')
    }

14.2 updating阶段

  • shouldComponentUpdate 性能优化 此函数必须有返回值,返回布尔值 默认为true 返回true 即更新 返回false 即不更新
  • componentWillUpdate 数据更新之前
  • render 数据渲染 (不要使用setState)
  • componentDidUpdate 数据更新之后
    // updating阶段
shouldComponentUpdate(){
    // 性能优化
    // 此函数必须有返回值,返回布尔值 默认为true
    console.log('should');
    return true; // 返回true 即更新 返回false 即不更新
}
componentWillUpdate(){
    console.log('更新之前')
}
componentDidUpdate(){
    console.log('更新之后')
}
myClick=()=>{
    let {num} = this.state;
    num++;
    this.setState({num})
}
render(){
    console.log('渲染')
    return(
        <div id="box">
            <button
                onClick = {this.myClick}
            >{this.state.num}</button>
            
        </div>
    )
}
  • 注意:updating阶段不要使用setState,否则会死循环
  • componentWillReceiveProps 父级数据发生变化
    • 一个组件要从父组件接收参数,如果这个组件第一次存在于父组件中,不会执行;如果这个组件之前已经存在于父组件中,才会执行

14.3 unmouting阶段

  • componentWillUnmout 当组件死亡时触发(卸载、跳路由、关定时器、数据重置、变量置空、清除事件)

页面什么时候会被渲染

    1. props或者state发生改变时,页面会重新渲染
    1. 父组件发生改变,子组件会跟随父组件重新渲染(消耗性能)
    • 性能优化方案:
    • 在子组件写一个生命周期函数 shouldComponentUpdate(nextProps,nextState),返回flase,父组件执行,子组件不会再更新

十五、在react中发送ajax请求

  • 安装模块 npm add axios
// todolist文件
//AJAX请求数据 只请求一次
componentDidMount(){
    axios.get('/api/todolist')
    .then(()=>{alert('success!')})
    .catch(()=>{alert('errer')})
}

15.1 使用Charles 进行接口数据模拟

经实践,charles无法请求到数据,原因不明

解决方案:

可以把假数据放在项目中的public目录下,也可以请求到数据