react开篇

本文详细介绍了React框架的基础知识,包括其轻量级视图层特性、生态系统、项目初始化及配置,深入解析了JSX语法、组件化开发、状态管理和生命周期函数。同时,探讨了受控与非受控组件的区别,以及属性传递机制。

react介绍

react是一个轻量级的视图层框架,用于构建用户界面的js库,核心是专注于视图层,目的是实现组件化开发
react生态:React + React-Router + Redux + Axios + Babel + Webpack

初始化react项目

  1. 全局安装create-react-app脚手架
// 如果已经安装了,直接进行第2步操作
npm install create-react-app -g
  1. 安装完成后就可以创建react项目
cd desktop  // cd到桌面上,执行下面操作
create-react-app 项目名  // 创建项目
  1. 项目创建完成后,cd到项目中,启动服务
cd 项目名  // cd到项目中,执行下面操作
yarn start  // 启动服务

服务启动完成后就会跳出来一个react页面

用编辑器打开项目文件,会发现文件夹里已经出现许多文件

文件简要介绍:

  • public文件:
    1、favicon.ico:react图标,可以替换成自己的图标,也可以直接删除
    2、index.html:html文件,不能删
    3、manifest.json:如果使用了PWA就可以对manifest.json进行配置,创建应用快捷键。
  • src文件:
    这里有三个文件比较特殊
    1、index.js:入口文件,除了此文件,src文件中其他的都可以删除
    2、App.test.js: 自动化测试文件。
    3、serviceWorker.js: PWA (progressive web application) 帮助我们借助网页写一些手机APP应用 https协议的服务器上,第一次打开需要联网,后面再次访问时就可以访问缓存的页面
  • .gitignore:不想在git上显示的文件可以在这里配置
  • package.json:项目依赖文件
  • README.md:备注,里面内容可删除,可以在这里编辑一些自己的备注
  • yarn.lock:依赖文件
    如果想要显示更多的文件,可以执行yarn eject,将隐藏的webpack配置文件config暴露出来,以便我们修改配置文件,如配置less文件

jsx语法

jsx是一种js和html混用的代码,由 Facebook 开发,代码示例:

let str = '<img src="https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo_top_86d58ae1.png" alt=""/>';
let arr = [1,3,4];
// jsx语法
let ele = (
    <>
    {/*当有2个或多个相邻的元素时,外面需要用一层标签包裹,如果不想让外面的标签占位,可以用<></>或者<Fragment></Fragment>表示*/}
        <h1 style={{backgeound:'red'}}>标题</h1>
		{/* 行内样式写法,style={{background:'red'}}一般是双大括号,外面表示里面是js语法,里面表示包裹的是对象,即一个js对象*/}
        <div>{1==1 ? <span>对的</span> : <span>错的</span>} 三元表达式</div>
		{/* jsx为了识别是html还是js分别用 <  和 { 区分,所以js外面要包层 {}  */}
		{/* {} 表示里面包裹的可能是 js、三元表达式、取值,只要有内容返回页面就能显示 */}
        <div className="box">定义类名用 calssName</div>
		{/* 属性的名字会有变化,例如 class是jsx里面的关键字,所以定义类名的时候 class转化为=>className,还有 for转化为=> htmlFor */}
        <label htmlFor="text">此时要用htmlFor代替for</label>
        {/* 将img插入到页面中  当前是一个危险的操作,不建议使用 */}
        <div dangerouslySetInnerHTML={{__html:str}}></div>
		{/* v-html把内容当成html插入到页面中 dangerouslySetlnnerHTML={{__html:xxx}} */}
		{/* 列表渲染用map 因为map默认有返回值,数组可以直接渲染到页面上 */}
		{arr.map((list,key) => (
                // key一定要有,是为了后续的diff算法比对数据用,循环谁就在谁上面加
				// key属性被内置了,不会作为属性传递
                <li key={key}></li>
         ))}
    </>
);

render函数

// 引入react核心包
import React from 'react'
// render,主要是用来渲染页面
import { render } from 'react-dom'

这里要注意的是,导入的React必须大写?
原因:
jsx语法是javascript + html混写,需要用babel转译。 babel转移时,会转换成 React.createElement 写法,要是react小写就识别不了。
代码如下:

// jsx 语法
let h1 = <h1 id="hello">hello world</h1>;
// 转移后的代码
React.createElement('h1',{id:'hello'},['hello world']);

通过 React.createElement 来创建一个 虚拟DOM,其中三个参数分别是type、props和children

  • type:对应的标签是h1
  • props:对应的是{id:'hello'},没有可直接用null代替
  • children:对应的子节点,可以是多个
    多个子节点的代码结构如下:
// 多个子元素(React.createElement层层嵌套)
let h1 = <h1 id="hello">hello world <span>你好</span></h1>;
// babel编译代码:
React.createElement('h1',{id:'hello'},['hello world',React.createElement('span',null,['你好'])]);

由此我们简单的梳理一下整个渲染的执行顺序
jsx语法 -> createElement格式 -> 转化成虚拟DOM(对象,可以描述当前元素) -> render函数渲染到页面上

基于此,我们可以实现一个简易版本的render函数

// 自定义模块
let React = {
    createElement(type,props,...children){
        console.log(...children);
        return {
            type,
            props,
            children
        }
    }
}
// 渲染函数
let render = (vnode,container) => {
    if(typeof vnode == 'string') return container.appendChild(document.createTextNode(vnode ));
    let {type,props,children} = vnode;
    let ele = document.createElement(type);
    for(let key in props){
        ele.setAttribute(key,props[key]);
    }
    children.forEach(child => {
        render(child,ele);
    });
    container.appendChild(ele);
};
let h1 = <h1 id="hello">hello world <span>你好</span></h1>;
// console.log(h1);
render(h1,window.root);

组件

react中,函数就是一个组件,并且组件名必须大写,是为了和jsx元素区分
组件分为:函数组件和类组件
函数组件:

  1. 没有this指向
  2. 没有生命周期
  3. 没有状态
import React from 'react'
// import ReactDOM from 'react-dom'
// 如果用的是上面的方法,没有解构render函数,下面的render()函数就变成ReactDOM.render()
import { render } from 'react-dom'
// 所有的组件都有属性(使用组件的地方,可以提供这些属性)
function Clock(props){
	// toLocaleString() 根据本地时间把Date对象转换为字符串,并返回结果
	return <h1>当前时间 {props.data.toLocaleString()}</h1>
// 渲染
render(<div>
	// date就是提供的属性
	<Clock date={new Date()}/>
</div>,window.root)
}

类组件:

  1. this指向的实例
  2. 有生命周期
  3. 有状态 this.state,它是个对象
// 实现一个类
// React.Component 是父类,它提供了一个更改自己的方法setState
class Clock extends Component{
    constructor(props){
        console.log(props);
        super(props);
        this.state = { // this.state这个名字不能修改,固定写法
            date:new Date().toLocaleString()
        };
    }

    // 跟上面的this.state是同一个,不同的写法,取一即可
    // state = {};

    // 生命周期函数 组件已经挂载完成
    componentDidMount(){
        this.timer = setInterval(()=>{
            this.setState({ // 调用setState会更新视图
                date:new Date().toLocaleString()
            });
        },1000)
    }

    // 组件将要更新
    componentWillUpdate(){
        // 在调用handleDelete前把定时器清除,不然会报错
        // 组件已经卸载,但是定时器还在执行,会出错
        clearInterval(this.timer);
    }

    handleClick(){
        console.log(this); // undefined 外界调用类里面的函数是不合法的,所以要绑定this
    }
    // 这两种改变this的优缺点:
    // 上面绑定this,每次调用函数都会产生一条函数
    // 下面的箭头函数不好传参数,要是传参数可以在调用的地方在包一层箭头函数 (params)=>this.handleDelete
    handleDelete = ()=>{
        console.log(this); // this指向的实例
        ReactDOM.unmountComponentAtNode(window.root);  // 从这个节点上卸载组件
    };

    render(){
        // console.log(this);
        return (<div>
                <h1>当前时间 {this.state.date} </h1>
                <button onClick={this.handleClick.bind(this)}>点击</button>
                <button onClick={this.handleDelete}>点击删除</button>
            </div>)
    }
}
render(<Clock />, window.root);

setState

import React,{Component} from 'react';
import {render} from "react-dom"
// 属性传到组件中不能更改
// 如果需要更改 把属性变为组件的状态
// 组件的数据来源 状态是自己的(可以更改),属性是外面的(不能更改)
class Counter extends Component{
    state = {
        count: this.props.count
    };

    handleClick = () => {

        // setState 批量更新的操作 但不是一直更新,只有第一次默认是批量更新
        // 多次执行setState,会批量执行,会进行合并,相同的key,后面的会覆盖前面的
        // this.setState({
        //     count: this.state.count +1
        // });
        // this.setState({
        //     count: this.state.count +2
        // });
        // this.setState({
        //     count: this.state.count +3
        // });

        // 为什么setState放到 setTimeout 中会渲染多次?
        // 会先执行transcation函数,此时isBatchingUpdate=false就不是批量操作,函数依次执行
        // setTimeout(()=>{
        //     this.setState({
        //         count: this.state.count +1
        //     });
        //     this.setState({
        //         count: this.state.count +2
        //     });
        //     this.setState({
        //         count: this.state.count +3
        //     });
        // },0);

        // setTimeout 这种操作是不合法的 react提供了下面这种方式
        // 每次都是基于上一次结果 在进行操作
        this.setState((prevState)=>({ count: prevState.count + 1 }));
        this.setState((prevState)=>({ count: prevState.count + 2 }));
        this.setState((prevState)=>({ count: prevState.count + 3 }));
        // => 等同于 可以在回调函数里面进行dom操作
        this.setState({count:this.state.count+1},()=>{
            this.setState({count:this.state.count+2},()=>{
                this.setState({count:this.state.count+3})
            })
        })

    };

    render(){
        return (<div>
            {this.state.count}
            <button onClick={this.handleClick}>添加</button>
        </div>)
    }
}
render(<Counter count={10}/>,window.root);

衍生的问题:为什么 setState 放到 setTimeout 中会渲染多次?

// 默认批量更新
let isBatchingUpdate = true;
let transcation = (component) => {
    component.state = component.pendingState;
    component.render();
    // 第二次就不默认批量更新了
    isBatchingUpdate = false;
};
class My {
    constructor(){
        this.state = {number:0};  // 自己的状态
        this.pendingState = { ...this.state }; // 队列 存放状态
    }
    setState(obj){
        if(isBatchingUpdate){
            this.pendingState = {...this.pendingState,...obj};
        }else {
            this.pendingState = {...this.pendingState,...obj};
            transcation(this)
        }
    }
    update(){  // setState批量更新函数
        /*
        在React的生命周期钩子和合成事件中,多次执行setState,会批量执行

        具体表现为,多次同步执行的setState,会进行合并,相同的key,后面的会覆盖前面的

        当遇到多个setState调用时候,会提取单次传递setState的对象,把他们合并在一起形成一个新的
        单一对象,并用这个单一的对象去做setState的事情,相同的key,后一个key值会覆盖前面的key值
        */
        this.setState({number: this.state.number + 1});
        this.setState({number: this.state.number + 2});
        this.setState({number: this.state.number + 3});
        // 为什么放到 setTimeout 中会渲染多次?
        // 因为会先执行transcation函数,此时isBatchingUpdate=false就不是批量操作,函数依次执行
        setTimeout(()=>{
            this.setState({number: this.state.number + 1});
            this.setState({number: this.state.number + 2});
            this.setState({number: this.state.number + 3});
        },0);
        transcation(this);
    }
    render(){
        console.log(this.state.number);
    }
}
let my = new My();
// 内部会先调用此函数
my.update();

导出的几种方式

// 如果是默认导出的
import xxx from './xxx'
// 如果是一个个导出的
import { xxx } from './xxx'
import * as xxx from './xxx'

受控组件

// 受控组件
// 和状态相关的,状态一变视图跟着变化  类似 双向数据绑定
import React,{Component} from 'react'
import ReactDOM from 'react-dom'

class Control extends Component{
    state = {
        username: 'zx',
        password: '123455'
    };

    handleChange = (e) => {
        // console.log(e.target.value);
        console.log(e.target);
        this.setState({
            // username: e.target.value,
            // 上面的写法只适合页面只有一个input时,如果有多个,需要给每个不同input绑定不同的函数
            // 下面的可以控制多个input,但需要给input设置name,并且需要和后面的状态名保持一致
            [e.target.name]: e.target.value
        })
    };

    handleClick = (e) => {
        e.preventDefault();
        alert(JSON.stringify(this.state))
    };

    render(){
        return (<div>
            {/*form 自带表单验证*/}
            <form onSubmit={this.handleClick}>
                {/*required 表单提交是自动校验输入框是否为空*/}
                <input required type="text" name="username" value={this.state.username} onChange={this.handleChange}/>
                <input type="text" name="password" value={this.state.password} onChange={this.handleChange}/>
                <button type="submit">提交</button>
            </form>
        </div>)
    }
}
ReactDOM.render(<Control/>,window.root);

非受控组件

// 如果只是点击获取输入框的值,推荐使用非受控组件
import React,{Component} from 'react'
import ReactDOM from 'react-dom'

class Control extends Component{

    // 与第二种配合使用 把当前的password绑定到current属性上
    password = React.createRef();

    handleClick = (e) => {
        e.preventDefault();
        console.log(this.username.value);
        // 第二种方式比第一种应用要多一层
        console.log(this.password.current.value);
    };

    render(){
        return (<div>
            {/*form 自带表单验证*/}
            <form onSubmit={this.handleClick}>

                {/*required 表单提交是自动校验输入框是否为空*/}
                {/*第一种方式*/}
                {/*dom 指的就是当前的输入框 把输入框挂在username的实例上 */}
                <input required type="text" name="username" ref={(dom)=>this.username=dom} />

                {/*第二种方式*/}
                <input type="text" name="password" ref={this.password}/>

                <button type="submit">提交</button>
            </form>
        </div>)
    }
}

ReactDOM.render(<Control/>,window.root);

属性传递 组件传值

组件传值是单向数据流

  • 1、父子组件可以通过属性传递
  • 2、平级组件之间传递是通过共同的父亲
  • 3、多级组件,跨组件传递通过 contentApi

prop-types 用来校验属性类型 (第三方插件)

import React,{Component} from 'react'
import ReactDOM from 'react-dom'
// 用来校验属性类型
import PropTypes from 'prop-types'

class Person extends Component {
    // 默认属性必须名字叫 defaultProps 属于类上的属性  es7
    static defaultProps = {
        name: 'zx'
    };

    // 专门用来校验类型的
    static propTypes = {
        age: PropTypes.number.isRequired,
        gender: PropTypes.oneOf(['男','女']),
        position: PropTypes.shape({
            x: PropTypes.number,
            y: PropTypes.number
        }),
        hobby: PropTypes.arrayOf(PropTypes.string),
        salary(props,propName,componentName){
            if(props[propName] <= 10000){
                throw new Error('收益太低')
            }
        }

    };

    render(){
        return <div>
            hello welcome {this.props.name}
        </div>
    }
}

let obj = {
    name:'zx',
    age: 27,
    gender: '男',
    position: {
        x: 100,
        y: 100
    },
    hobby:['睡觉'],
    salary: 1000
};

export {
    Person,
    obj
}

ReactDOM.render(<Person {...obj}/>,window.root);

转载于:https://my.oschina.net/bangxia/blog/2964040

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值