前言

学习React是今年的计划之一,再加上之前学习了Vuejs,想了解两者之间的区别,因此大致学习下React,有机会再用React写一个项目。

React的三大体系

  • 用于Web开发和组件的编写
  • ReactNative用于移动端开发
  • ReactVR用于虚拟现实技术的开发

    安装

    安装Nodejs

    使用Reactjs最原始的方法就是script标签引入,但这太low了,并且在工作当中也不会这样引用,因此安装Nodejs来使用react最佳~打开下面的网址,自行安装即可。 Nodejs中文网址:http://nodejs.cn/

    脚手架安装

    安装完Nodejs后,使用npm命令安装脚手架
    npm install -g create-react-app

    创建第一个React项目

    create-react-app demo

    目录结构

  • src:项目代码主目录
  • public:公共文件,例如可以存储样式文件、图标等等
  • node_modules:项目的依赖包
  • gitignore:git的选择性上传配置文件
  • package-lock.json:锁定安装时的版本号,以保证其他人再npm install时大家的依赖能保证一致

    src文件夹

  • index.js:项目的入口文件
  • index.css:index.js里的css文件
  • app.js:相当于一个方法模块,也是一个简单的模块化编程
  • serviceWorker.js:用于移动端开发,PWA必须用到这个文件,有了这个文件,就相当于有了离线浏览的功能

    编写第一个HelloWord

    index.js 入口文件的编写

    src目录下,新建一个文件index.js,写入下面4行代码
    import React from 'react'
    import ReactDOM form 'react-dom'
    import App from './App' #这里其实省略了.js,完整是./App.js,但这里可以省略,react会自动识别
    ReactDOM.render(<App />,document.getElementById('root'))
    上面的代码,首先引入了React必要的两个文件,然后引入了一个APP组件,再使用ReactDOM渲染到了rootID上.PS:这个root ID是在public\index.html中。

    app组件的编写

    import React, {Component} from 'react'  #ES6的语法-解构赋值,当然也可以写成下面两行
    // import React from 'react'
    // const Component = React.Component
    class App extends Component{
      render(){
          return (
              <div>
                  Hello React
              </div>
          )
      }
    }
    export default App;

    React中JSX语法

    简介

    javascript + xml = jsx。当遇到<,JSX就当作HTML解析,遇到{就当JS解析,举个例子(JSX语法)
    <ul className="list">
      <li>视觉志</li>
      <li>I love React</li>
    </ul>
    而在以前写一段JS:
    var child1 = React.createElement('li', null, '视觉志');
    var child2 = React.createElement('li', null, 'I love React');
    var root = React.createElement('ul', { className: 'list' }, child1, child2);
    从代码量上看出JSX语法大量简化了我们的工作.

    小坑

    自定义的组件必须首写字母要进行大写,而JSX是小写字母开头的

    使用三元运算符

    import React from 'react'
    const Component = React.Component
    class App extends Component{
      render(){
          return (
              <ul className="my-list">
                  <li>{false?'视觉志':'iobiji.com'}</li>
                  <li>I love React</li>
              </ul>
          )
      }
    }
    export default App;

    实例

    新建一个组件

    import React,{Component} from 'react'
    class Demo extends Component{
      render(){
          return  (
              <div>
                 <ul>
                     <li>aaa</li>
                     <li>bbb</li>
                 </ul> 
              </div>
          )
      }
    }
    export default Demo;

    组件外层包裹原则

    划重点,比如上面的代码,去掉最外层的<div>就会报错,因为React要求必须在一个组件的最外层进行包裹,这就类似于Vuejs中的只允许一个根div

    Fragment标签

    加上最外层的DIV,组件就是完全正常的,但布局就偏不需要这个最外层的标签怎么办?比如在作Flex布局时,外层还真的不能有包裹元素。这种矛盾其实React已经给了我们解决方案,也就是<Fragment>标签 引入:
    import React,{Component,Fragment} from 'react'
    然后把最外层的<div>换成标签即可.

    响应式设计和数据的绑定

    React不建议直接操作DOM元素,而是要通过数据进行驱动,改变界面中的效果。React会根据数据的变化,自动的帮助你完成界面的改变。所以在写React代码时,你无需关注DOM相关的操作,只需要关注数据的操作就可以了(这也是React如此受欢迎的主要原因,大大加快了我们的开发速度)。 数据定义在组件中的构造函数里constructor
    constructor(props){
      super(props) //调用父类的构造函数,固定写法
      // 还有就是一般this在这里绑定,特别是在高级组件开发中,会有很大的作用
      this.state={
          inputValue:'' , // input中的值
          list:[]
      }
    }
    React中的数据绑定和Vue中几乎一样,就是使用{}来标注,其实这也算是js代码的一种声明。比如现在我们要把inputValue值绑定到input框中,只要写入下面的代码就可以了。其实说白了就是在JSX中使用js代码。
    <input value={this.state.inputValue} /> 

    绑定事件

    这时候到界面的文本框中去输入值,是没有任何变化的,这是因为我们强制绑定了inputValue的值。如果要想改变,需要绑定响应事件,改变inputValue的值。比如绑定一个改变事件,这个事件执行inputChange()(当然这个方法还没有)方法。 在render()方法下建立一个inputChange()方法:
    inputChange(e){
    console.log(e.target.value);
    this.state.inputValue = e.target.value;
    }
    其实,这里犯了两个错误:
  1. this的指向不对,需要重新用bind设置指向(ES6的语法)

  2. React中改变数据需要使用this.setState方法 解决:

    <input value={this.state.inputValue} onChange={this.inputChange.bind(this)} />

    另外需要在inputChange方法加入setState方法来改变值:

    inputChange(e){
     // console.log(e.target.value);
     // this.state.inputValue=e.target.value;
     this.setState({
         inputValue:e.target.value
     })
    }

    key值错误解决

    <ul>
     {
         this.state.list.map((item,index)=>{
             return <li key={index+item}>{item}</li>
         })
     }
    </ul>  

    数组下标的传递

    <ul>
     {
         this.state.list.map((item,index)=>{
             return (
                 <li 
                     key={index+item}
                     onClick={this.deleteItem.bind(this,index)}
                 >
                     {item}
                 </li>
             )
         })
     }
    </ul>  

    遍历

    <ul>
     {
         this.state.list.map((item,index)=>{
             return (
                 <ServiceItem 
                 key={index+item}  
                 list={item}
                 index={index} />
             )
         })
     }
    </ul> 

    JSX防踩坑的几个地方

    JSX代码注释

     //第一次写注释,这个是错误的
     <div>
         <input value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
         <button onClick={this.addList.bind(this)}> 增加服务 </button>
     </div>

    正确的写法有以下两种:

    # 第一种
    {/* 正确注释的写法 */}
    # 第二种
    {
    //正确注释的写法 
    }

    第二种不太优雅,推荐第一种注释方法,当然使用编辑器的快捷注释也可以,最简单的方法

    JSX中的class陷阱

    使用class属性是错的,必须使用className,它是防止JS中的class类名冲突。

    <input className="input" value={this.state.inputValue} onChange={this.inputChange.bind(this)} />

    JSX中的html解析问题

    如果想在文本框里输入一个<h1>标签,并进行渲染。默认是不会生效的,只会把<h1>标签打印到页面上,这并不是我想要的。如果工作中有这种需求,可以使用dangerouslySetInnerHTML属性解决。具体代码如下:

    <ul>
     {
         this.state.list.map((item,index)=>{
             return (
                 <li 
                     key={index+item}
                     onClick={this.deleteItem.bind(this,index)}
                     dangerouslySetInnerHTML={{__html:item}}
                 >
                 </li>
             )
         })
     }
    </ul> 

    JSX中<label>标签的坑

    先看下面的代码,我们在文本框前面加入一个<label>

    <div>
     <label for="service">加入服务:</label>
     <input id="service" className="input" value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
     <button onClick={this.addList.bind(this)}> 增加服务 </button>
    </div>

    console会有红色警告提示的。大概意思是不能使用for.它容易和javascript里的for循环混淆,会提示你使用htmlFor

    <div>
     <label htmlFor="service">加入服务:</label>
     <input id="service" className="input" value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
     <button onClick={this.addList.bind(this)}> 增加服务 </button>
    </div>

    组件的拆分

    新建一个组件

    import React, { Component } from 'react';
    class ServiceItem  extends Component { //cc
    
     render() { 
         return ( 
             <div>服务列表</div>
          );
     }
    }
    export default ServiceItem;

    第二种方法

    import React from 'react';
    const ServiceItem = (props) => {
     <div>服务列表</div>
    }
    export default ServiceItem;

在另外一个组件中使用import引入这个组件:

import ServiceItem from './ServiceItem'

然后直接写入<ServiceItem />标签即可

父子组件的传值

使用组件属性的形式,父组件传值给子组件。比如加入list属性,然后给属性传值{item},这样就完成了父组件向子组件传值.

<ServiceItem list={item} />

在接收数据的组件通过使用this.props.xxx的形式接收即可,例如

import React, { Component } from 'react';
class ServiceItem  extends Component { //cc

    render() { 
        return ( 
            <div>{this.props.list}</div>
         );
    }
}
export default ServiceItem;

子组件向父组件传递数据

首先子组件绑定点击事件:

import React, { Component } from 'react';

class ServiceItem  extends Component {
    render() { 
        return ( 
            <div onClick={this.handleClick}>{this.props.list}</div>
         );
    }

    handleClick(){
        console.log('点击了')
    }

}

export default ServiceItem;

父组件向子组件传递点击删除方法

<ul>
    {
        this.state.list.map((item,index)=>{
            return (
                <ServiceItem 
                key={index+item}  
                list={item}
                index={index}
                //关键代码-------------------start
                deleteItem={this.deleteItem.bind(this)}
                //关键代码-------------------end
                />
            )
        })
    }
</ul>

子组件调用父组件传递的方法

import React, { Component } from 'react';
class ServiceItem  extends Component {
   constructor(props){
       super(props)
       this.handleClick=this.handleClick.bind(this)
   }

    render() { 
        return ( 
            <div onClick={this.handleClick}>
                {this.props.list}
            </div>
        );
    }
    handleClick(){
        this.props.deleteItem(this.props.index)
    }
}

export default ServiceItem;

ref的使用方法

代替原来的e.target.value

以前的案例中使用了e.target,这并不直观,也不好看。这种情况可以使用ref来进行解决。

inputChange(e){
    this.setState({
        inputValue:e.target.value
    })
}

如果要使用ref来进行,需要现在JSX中进行绑定, 绑定时最好使用ES6语法中的箭头函数,这样可以简洁明了的绑定DOM元素。

<input 
    id="addService" 
    className="input" 
    value={this.state.inputValue} 
    onChange={this.inputChange.bind(this)}
    //关键代码——----------start
    ref={(input)=>{this.input=input}}
    //关键代码------------end
    />

绑定后可以把上边的类改写成如下代码:

inputChange(){
    this.setState({
        inputValue:this.input.value
    })
}

React和Vue的对比

React.js相对于Vue.js,它的灵活性和协作性更好一点,个人认为react是UI框架,而vue是数据驱动框架,因为vue.js有着丰富的API,实现起来更加简单快速。