初识react

242 阅读7分钟

简介

react 是一个用于构建用户界面的javascript库,他比较简单,模式就是组件化开发,方便管理,将1个大的页面划分成若干小组件,当然他也有路由模块,redux等,他可以将每个组件的逻辑和样式放到一起去。

组件化优点

  • 可组合
  • 可重用
  • 可维护

搭建脚手架

我们知道 react使用的语法是jsx (javascript xml),在之前如果说php是JS IN HTML那么react可以说是HTML IN JS,对于jsx我们需要用babel解析,官方给推荐我们用脚手架,直接安装各种依赖,html属于xml.

npm install create-react-app -g

然后去当前项目文件夹创建项目

create-react-app myreact

安装之后会出现个myreact的文件夹,文件夹里有pakage.json文件

{
  "name": "myreact",
  "version": "0.1.0",
  "private": true,
  "dependencies": { //依赖
    "react": "^16.4.1",
    "react-dom": "^16.4.1",
    "react-scripts": "1.1.4"
  },
  "scripts": {
    "start": "react-scripts start",  //启动命令
    "build": "react-scripts build",  // 构建
    "test": "react-scripts test --env=jsdom", //测试
    "eject": "react-scripts eject" //webpack配置
  }
}

我们可以清晰的看到react可以自动帮助我们安装react 和react-dom的包,那么他们是干什么的呢?

与vue不同的是,vue有大量的api,,但是react的核心就几个,比如

  • 虚拟DOM(对象)
  • DOM_DIFF

(jsx像html,但是不是html) facebook 提供的jsx语法,可以将xml 'babel'成react对象,,比如ajax就是解析异步的xml,react渲染到页面的整个过程如下

babel转移JSX语法 => react语法 => 虚拟Dom => 插入到页面中

问们可以看到,在src的js里面,用的import,这个是webpack语法,import React from 'react';相当于node中的let {React} require('react');

react特点

  • 在index.js引入 home.js
//index.js
import './home';


//编辑home.js 
import  React from 'react'; //创建元素
import {render} from 'react-dom'; //渲染页面dom
import './home.css';
//let element = <h1>hello baby <span>child</span></h1>
console.log(element)

//可以输出一个虚拟DOM,我们说虚拟dom其实是一个对象如下,babel编译出的react对象,大致特点

//我们可以模拟React, render
let React = {
    createElement(type, props , ...children){ //类型,属性,儿子
        return {type , props , children}
    }
}
function render(vnode,container){
    console.log(vnode)
    console.log(typeof vnode === "string")
    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);
}

jsx语法特点

  • React.Fragment 无意义标签,相邻的两个jsx元素/react元素不能并列写,渲染数组,必须包起来
  • 属性
  • 不能用关键字
  1. class => className
  2. style={"color:red"} 将内容转换为对象
  3. 写注释{/注释/}
  4. for => htmlfor
  5. 将字符串变成标签 danerouslySetIneerHtml{{__html:str}} xxs攻击
  • {}表达式的用法<%=%>
  • 表达式取值得有返回值(函数可以返回jsx) 1.{放表达式/自执行函数/有返回值的函数执行}
  1. {abc + 'sdf' + saf}
  2. {不能放if表达式,,用三目运算符} null || void 0 都是空,合法的jsx语法
import  React from 'react'; //创建元素
import {render} from 'react-dom'; //渲染页面dom

let arr = ['不吃饭','不睡觉'];

//使用map时需要增加key
 function toLis(){
     return arr.map((item,index)=>(<li key = {index}>{item + index}</li>))
 }
let ele =  <div>{toLis(1)}</div>;
console.log(ele)
render(ele, document.getElementById('root'))

组件化(复用,提高可维护性)

  • 组件由react元素组成,组件就是一个函数
  • 组件(函数组件,类组件) 属性 , 状态
  • 假设我们要一个计时器,我们可以如下
function clock(time){
    return <h4>时间是:{JSON.stringify(time)}</h4>
}


let ele = <div></div>
render(<div>{clock(new Date().toLocaleString())}</div> ,document.getElementById('root'))

使用函数组件

我们可以看到,将函数作为标签,bebel会将这个标签自动编译成element对象,方便render渲染,所以我们就可以直接渲染

function Clock(time){
    return <h4>时间是:{JSON.stringify(time)}</h4>
}
// 组件传值,会自动将属性值传给函数组件
render(<Clock data={new Date().toLocaleString()} data_in="哈哈"></Clock> ,document.getElementById('root'))

结果=>
时间是:{"data":"2018/7/1 下午4:56:21","data_in":"哈哈"}

由此可见 我们想要获取 时间只需如下

function Clock(props){
   return <h4>时间是:{props.data}</h4>
}

我们想每秒刷新时间,我们可以看见,这种方法只会渲染一次,所以我们可以是这每秒刷新

setInterval(() =>{
    render(<Clock data={new Date().toLocaleString()}></Clock> ,window.root)
},1000)

这样我们成功实现了计时,看dom渲染中,我们可以清晰看出仅仅是time变化,而不是标签变化,这里涉及到我嗯dom-diff算法

同时,函数组件的缺点是:

  • 没有生命周期
  • 没有this
  • 没有自己状态

如果需要以上之一,我们需要写类组件,那么类组件如何实现? 首先我们需要react 里的Component类

import  React , {Component} from 'react'; //创建元素
import {render} from 'react-dom'; //渲染页面dom

class Clock extends Component{
    constructor(props){
        super();
        this.state = {data:new Date().toLocaleString()}
    }
    componentDidMount(){// 组件挂在完成
        console.log('didmoount')
        setInterval(()=>{
            //将这个对象和原有状态进行合并,合并后的结果重新渲染界面
            this.setState({data:new Date().toLocaleString()})
        },1000)
    }
    render(){ //每一个类组件都有一个render方法,将render的返回值作为结果进行渲染
        console.log('render');
        return (
            //在render方法中可以通过this.props获取属性
            <React.Fragment>
                <p>{this.props.name}</p>
                <div>{this.state.data}</div>
            </React.Fragment>
            
        )
    }
}
render(<Clock name="当前时间是:"></Clock> ,window.root)


当然还有另一种我们经常用的方法

import  React , {Component} from 'react'; //创建元素
import {render} from 'react-dom'; //渲染页面dom

class Clock extends Component{
    constructor(props){
        super();
        this.state = {data:new Date().toLocaleString()}
    }
    getTime() {
        setInterval(()=>{
            //将这个对象和原有状态进行合并,合并后的结果重新渲染界面
            this.setState({data:new Date().toLocaleString()})
        },1000);
    }

    render(){ //每一个类组件都有一个render方法,将render的返回值作为结果进行渲染
        const self = this;
        self.getTime();
        return (
            //在render方法中可以通过this.props获取属性
            <React.Fragment>
                <p>{self.props.name}</p>
                <div>{self.state.data}</div>
            </React.Fragment>
            
        )
    }
}
render(<Clock name="当前时间是:"></Clock> ,window.root)

设置默认属性和属性校验

import  React , {Component} from 'react'; //创建元素
import {render} from 'react-dom'; //渲染页面dom

//校验属性的正确性  npm install prop-types 
import PropTypes from 'prop-types';

class Person extends Component{
    //Es6不支持静态属性, 只有静态方法  这个是ES7的 
    static propTypes = {
        name: PropTypes.string.isRequired ,
        age : PropTypes.number,
        gender:  PropTypes.oneOf ([ '男' ,'女']),
        hobby :  PropTypes.arrayOf(PropTypes.string),
        pos:  PropTypes.shape({x: PropTypes.number,y: PropTypes.number}),
        salary(props,propty){
            if(props[propty] > 3000){throw new Error('salary too big')}
            return props[propty] < 3000;   
        }
    }
    //设置默认属性
    static defaultProps = {name:'zdl'}
    constructor(props){super();}
    render(){return (<h1>{this.props.age}</h1>)}
}
let person = {
    name : 100 ,
    age : '20' ,
    gender: '男' ,
    hobby : ['吃饭', '睡觉'],
    pos: {x:100, y:200},
    salary: 5000
}
render(<Person {...person}></Person>, window.root)

React生命周期

React最重要,最常用的就是生命周期,详情www.cnblogs.com/qiaojie/p/6…

这里我们只提供部分测试代码

//生命周期

import  React , {Component} from 'react';
import ReactDom,{render} from 'react-dom'; 

class ChildCounter extends Component{
    constructor(){
        super();
        this.state = {};
    }
    // 挂载之前,改组件会触发多次(不推荐使用),可以在constrctor初始化值
    // componentWillMount(){
    //     console.log("child将要挂载");
        
    // }
    render(){
        return <div>{this.props.num}</div>
    }
    // componentWillUpdate(){
    //     console.log("child更新完成");
    // }
    componentDidMount(){
        console.log(this.state.num);
        
        console.log("child挂载完成");
    }
    static getDerivedStateFromProps(newProps){
        //ChildCounter使用getDerivedStateFromProps(),但也包含以下传统生命周期:
        //   componentWillMount
        //   componentWillReceiveProps
        //   componentWillUpdate

        // 应删除上述生命周期
        console.log("接受新属性新");
        return {num : 1} //会将之前的状态覆盖掉
        
    }
    getSnapshotBeforeUpdate(prevProps , prevState){//更新前的属性和更新前的状态
        console.log("更新前的属性和状态" + prevProps + prevState);
        return '123'; //返回的值会传到componentDidUpdate里去
    }
    componentDidUpdate(a,b,c){
        console.log("配合getSnapshotBeforeUpdate更新完毕" , a,b,c);
        
    }

    // //第一次不会执行,而且是唯一可以调用setState属性,已经被替换了
    // componentWillReceiveProps(newProps){//接收到新的属性之后才会执行
    //     console.log("child接受到新属性");
        
    // }
    //只要调用setStat就会更新视图  返回false不更新   优化  immutablejs 
    shouldComponentUpdate(nextProps , nextState){
        console.log("child将要更新视图");
        return true;
    }
}
class Person extends Component{
    //内部可以声明状态,获取属性
    constructor(props){
        super();
        this.state = {num: 0}
        //this.fn = this.fn.bind(this)
    }
    // fn(){
    fn = () =>  { 
        this.setState((prevState) => ({num: prevState.num + 1}));
        // this.time = setInterval(()=>{
        //     this.setState((prevState) => ({num: prevState.num + 1}));
        // },1000)
    }
    // 挂载之前,改组件会触发多次(不推荐使用),可以在constrctor初始化值
    componentWillMount(){
        console.log("将要挂载");
        
    }
    //卸载组件
    remove = () =>{
        console.log("销毁");
        ReactDom.unmountComponentAtNode(window.root)
    }
    //组件销毁之前
    componentWillUnmount(){
        console.log("销毁之前");
        clearInterval(this.time)
    }
    render(){
        console.log("渲染");
        return (<div>
            {this.state.num}
            <button onClick={this.fn}> + </button>
            <button onClick={this.remove}> 删除组件</button>
            <ChildCounter num={this.state.num}></ChildCounter>
        </div>)
    }
    //组件挂载完成
    componentDidMount(){
        console.log("组件挂载完成");
        
    }
    //只要调用setStat就会更新视图  返回false不更新   优化  immutablejs 
    shouldComponentUpdate(nextProps , nextState){
        console.log("更新视图");
        return nextState.num%2;
    }
}
let person = {
    name : 'person'
}

render(<Person {...person}></Person>, window.root)