若是你也刚入门React,来一块儿学习吧

本文提要

本文主要写一些CRA脚手架的安装,React的语法,组件分类和组件传值等;若是您是已经在React上有丰富经验的开发者,欢迎指出文中有问题和能够改进的地方,对此我将表示感谢!
这是react的官方站点React官网javascript

本文将主要分为:css

  • React项目搭建
  • React语法
  • React组件介绍
  • React组件传值
  • 一个简单的组件化小例子
  • 本文暂时不介绍React-Router

阅读全文可能会花费您10-20分钟,若是以为有兴趣,能够一块儿敲敲代码
(使用编辑器:VSCODE,插件:VS Code ES7 React/Redux/React-Native/JS snippets,这个插件能够快速构建组件格式,若是想要练练手的同窗就不要用快速指令了哦)html

下面开始正文

1.经过脚手架建立React项目

First thing first,这里咱们利用create-react-app(须要nodejs环境)来建立这个项目,毕竟比较方便嘛,有其余建立项目和服务的方式也可使用。vue

找一个工做文件夹,而后打开命令行工具,输入 create-react-app mycode就能够建立一个文件夹为 mycode的项目文件夹,注意哦,这个项目名称不支持大写字母
ok,几分钟后会提示你脚手架初始化成功了

这里有几个指令:

  • npm start开启开发服务器,通常默认是3000端口,启动后会自动弹出localhost:3000的页面
  • npm run build为生产环境建立打包的静态文件
  • npm test开启测试,这个我没有用过,有用过的同窗能够在评论里分享一下使用技术文章
  • npm run ejectEject 将全部的工具(配置文件和 package.json 依赖库)解压到应用所在的路径,这个过程是不可逆的

那咱们开始吧,cd mycode & npm startjava

2.基本语法

项目中两个重要的文件

咱们启动后会看到这个界面,这是脚手架自带的。 请移步到编辑器,这里咱们暂时只关注两个重要的文件

  • public/index.html 由于React搭建的是SPA,因此index.html是咱们的主页,在文件中你也能够看到<div id="root"></div>,root就是根组件渲染的位置。
  • src/index.js 这是咱们的主要的js文件,
    其中这一句表达式:render(<App></App>, window.root)代表,咱们使用一个渲染方式render,将App渲染到root中去,不论App中有什么,有多少层级,有多少组件,有多少逻辑,最终只有这一个入口。

React中的一切都从这里开始

咱们将脚手架src下的全部文件所有删掉,建立一个空白的index.js,开始coding。node

index.js中,咱们要作的就是,引入React库,引入react-dom,引入根组件,而后执行根组件的渲染方法:react

  • import React from 'react' 注意这里的React必须首字母大写
  • import {Component} from 'react',引入组件方法,使用{Component}解构方式引入
  • import App from './App',引入根组件App,咱们再下一步将会建立一个src/App.js做为咱们的根组件,这里你能够取任何名字做为你的根组件js,我习惯取做App
  • render(<App></App>, window.root)渲染组件到root容器,render是react的核心渲染方法,后面咱们会一直用到

语法介绍

① jsx简介

按照上一节的引入各类库,咱们能够在index.js中coding来学习React的基础语法。
简要来讲,react的核心语言是jsx,按照官方文档的举例:ios

const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;

ReactDOM.render(
  element,
  document.getElementById('root')
);
复制代码

咱们在React下写的<h1><h1>这样的html标签,实际上都会按照上面的代码渲染到页面上,只不过做为一个很sweet的语法糖,咱们不须要再写ReactDOM.render(),而是git

let el = <h1>hello <span>world</span></h1>
render(el,window.root);
复制代码

使用javascript + xml语法,定义一个元素,而后再render渲染就能够了;在index.js中,你能够先注释掉本来的render(<App></App>, window.root)这句话,改成上面的代码,在localhost:3000中能够看到es6

hello world渲染上了页面;(后续若是有时间,会写一篇小文讲解一下react虚拟dom的实现原理)

② <>和{}

用一句话来归纳就是: jsx元素/react元素 用<号标识, 看到{ 会认为里面包含的是js代码

1){}中执行js

  • 变量取值
let str = '<h1>world</h1>'
let el = (
  <div>
    <div>{str}</div>
  </div>
)
render(el,window.root);
复制代码

页面效果如图

str变量在{}中执行,div中间的内容应该是字符串'<h1>world</h1>',而不是标签h1

  • 注销

若是咱们想在代码中写备注怎么办?是这样吗//,哦不行,加个{//}呢?哦不行,你在编辑器中能够看到

这后面的}也被注销了,因此在react中,咱们使用 {/*hello*/}的方式做为备注

  • 字符串解析为html
    在第一个小例子里面提到 '<h1>world</h1>'会被做为字符串内容渲染,可是若是确实想要做为dom展现h1呢?
    这里咱们使用 <div dangerouslySetInnerHTML={{ __html: str }}></div>,这个API很长对吧,咱们在容器上标注 dangerouslySetInnerHTML~危险地设置innerHTML

    这么作可能有被插入恶意执行脚本的的风险。

  • 执行一个方法

function a() { 
  return <h3>hello function</h3>
}

let el = (
  <div>
    <div>{a()}</div>
  </div>
)
复制代码

页面:

  • 循环
    添加一个li key的要时最好不要用数组的索引,为了能标示每一个循环元素便于dom-diff, 通常用id,这里因为例子比较简单,咱们使用数组的索引填写key ;使用arr.map的方式处理数组返回
let arr = [1,2,3];
// 
let el = (
  arr.map((item, key) => (
    <li key={key}>{item}</li>
  ))
)
复制代码

  1. jsx中html属性的几个特色
  • classclassName 这个驼峰方式的写法代替了原生html的class,可是class仍是能够用的,脚手架会提示你这里应当使用className

  • forhtmlFor 这个for是html的label上的for,用于指向控制的input,在jsx中咱们使用htmlFor来替代

<label htmlFor="username">用户名</label>
<input type="text" id="username" />
复制代码
  • <div style="color:red">hello</div><div style={{ color: 'red' }}>hello</div> 外层的{}表示js语法的标示,{ color: 'red' }是对象

  • React.Fragment
    若是有用过vue的同窗应该知道,vue返回的html必定要有一个根节点包裹,即返回的dom必定是一个,不能是平级的多个,即

<div>
    <div></div>
    <p></p>
</div>
复制代码

在react中一样,若是咱们返回平级的多个div的话:

react会提示语法错误:jsx必须被一个闭合标签包裹

可是若是咱们在某种状况下,必须使用一些平级元素怎么办呢,好比处于样式的考虑,咱们外层没有什么须要div包裹的。这时候咱们使用<React.Fragment>来包裹平级的元素,这个<React.Fragment>是没有实际意义的,就充当一个节点闭合标签。

let el1 = (
  <React.Fragment>
    <div>{str1}</div>
    <div>{a()}</div>
    <div>{JSON.stringify(obj)}</div>
    <div>{false?<span>你好</span>:void 0}</div>
  </React.Fragment>
)
复制代码

这样就不会报错了

总的来讲,react的API较少,写jsx是很自由的,js+xml的方式,使js功底很深厚的开发者能够在html中任意的书写js逻辑,所写即所得,可能这就是react的魅力吧。

3.组件

在react项目中,基本上全部的结构功能均可以拆分红很细的一个个组件,好比一个页面上经常使用的菜单栏,能够拆分红:列表框List,列表项ListItem,列表连接Link等等,这样的好处是:
1.复用 2.方便维护 3.提升工做效率。

react声明组件的方式分为函数声明和类声明

  • 函数式声明组件
    函数式声明组件的方式以下
function Build(props) {
  let {title,content} = props;
  return (
    <div>
      <div>{title}</div>
      <div>{content}</div>
    </div>
  )
}

render(<div>
  <Build title="build1" content="content1"></Build>
  <Build title="build2" content="content2"></Build>
  <Build title="build3" content="content3"></Build>
</div>, window.root);
复制代码

若是咱们仅须要展现一些信息到页面上,不须要去控制变化,则函数组件能够简单实现
组件的定义必定要是首字母大写的,函数式组件传值的方式是按照在组件中定义了属性名,在组件使用时直接写在组件上 <Build title="build3" content="content3"></Build>
函数组件的缺点是 1.没有this 2.没有状态 3.没有声明周期 能够经过定时器能够实现函数式组件中值的定时改变,好比这个例子

function Clock(props) {
  return <div> 时间更新:<span>{props.time}</span></div>
}
setInterval(()=>{
  render(<Clock time={new Date().toLocaleString()} />, window.root);
},1000)
复制代码
  • 类声明
    使用es6建立类的方式建立组件,类声明的组件拥有了状态,视图经过setState方法进行更新 以后咱们作的小例子的组件,都用类声明的方式进行建立。在使用中来学习使用

受控组件 和 非受控组件

非受控组件:表单数据由DOM自己处理。即不受setState()的控制,与传统的HTML表单输入类似,input输入值即显示最新值(使用 ref 从DOM获取表单值)
受控组件:在HTML中,标签<input><textarea><select>的值的改变一般是根据用户输入进行更新。在React中,可变状态一般保存在组件的状态属性中,而且只能使用 setState() 更新,而呈现表单的React组件也控制着在后续用户输入时该表单中发生的状况,以这种由React控制的输入表单元素而改变其值的方式,称为:“受控组件”。

这里咱们写一个非受控组件的小例子,咱们输入的值经过点击显示出来;非受控组件经常使用于操做dom,较为方便

import React,{Component} from 'react';
import {render} from 'react-dom';
class UnControl extends Component{
  b=React.createRef();
  handleClick = () =>{
    alert(this.a.value); // 写法1
    alert(this.b.current.value) // 写法2
  }
  render(){
    return (<div>
      <input type="text" id="username" ref={dom=>this.a=dom}/>
      <input type="text" id="password" ref={this.b}/>
      <button onClick={this.handleClick}>点击</button>
    </div>)
  }
}
render(<UnControl></UnControl>, window.root);

复制代码

接下来咱们将实现这样的一个小例子:

实现一个评论组件,相似于掘金下方的评论栏,咱们将这个组件大功能拆分为
根组件App,列表组件List,列表项ListItemComment评论组件,在实现的过程当中,咱们会讨论组件间数据传递的方式。

4.更新视图的方法

首先,咱们不拆分组件,将上述的例子简单构建出来,页面结构使用bootstrap UI(npm install boostrap@3) 组件。
在这个例子中,咱们采用axios(npm install axios)请求初始列表数据,封装为一个request.js,代码以下:

import axios from 'axios';

axios.interceptors.response.use(function (res) {
  if (res.data.code === 0) {
    return res.data.users
  } else {
    return Promise.reject('错误');
  }
})

export default axios
复制代码

请求的数据格式本身简单拟定为:

{
    "code":0,
    "users": [
      {
        "id": 1,
        "avatar": "http://05.imgmini.eastday.com/mobile/20171112/20171112104845_40c4a989ba5a02f512b05336bff309f8_1.jpeg",
        "username": "Jim",
        "content": "Hi,你的文章很不错"
      },
      {
        "id": 2,
        "avatar": "http://05.imgmini.eastday.com/mobile/20171112/20171112104845_40c4a989ba5a02f512b05336bff309f8_1.jpeg",
        "username": "Jim",
        "content": "通常般的说"
      }
    ]
  }
复制代码

而后贴出咱们的App.js,咱们将所有的内容都放在App.js中,不拆分组件:

import React, { Component } from 'react';
import axios from './request'
import 'bootstrap/dist/css/bootstrap.css'
import './Common/common.css'

class App extends Component {
    state = {
        users: [],
        count: 0,
        id: 3
    }
    // 点赞功能
    increment = () => {
        this.setState({
            count: this.state.count + 1
        })
    }
    // 添加评论
    addComment = (val) => {
        let id = this.state.id;
        let users = [...this.state.users, { avatar:  "http://05.imgmini.eastday.com/mobile/20171112/20171112104845_40c4a989ba5a02f512b05336bff309f8_1.jpeg", content: val, username: 'Jim', id: id}];
        this.setState({
            users
        });
        this.state.id+=1;
    }
    content = React.createRef();
    // 提交数据
    handleSubmit = (e) => {
      e.preventDefault();
      this.addComment(this.content.current.value);
  }
    // 删除一条
    removeById = (id) => {
        let users = this.state.users.filter(user=>user.id!==id); // 排除列表里相同id的,即达到删除的目的
        this.setState({
            users
        })
    }
    // 获取列表数据
    async componentDidMount() {
        let users = await axios.get('/users.json');
        this.setState({
            users
        });
    }
    render() {
        return (
          <div className="container">
          <div className="panel panel-danger">
              <div className="panel-heading">
                  评论
              </div>
              <div className="panel-body">
              {
                this.state.users.map((user, index) => {
                  return (
                    <div className="media">
                    <div className="media-left">
                        <img className="avatar" src={user.avatar} />
                    </div>
                    <div className="media-right">
                        <h3>{user.username} </h3>
                        <div>评论:{user.content}</div>
                        <button className="btn btn-danger" onClick={(e)=>{
                            this.removeById(user.id)
                        }}>删除</button>

                    </div>
                </div>
                  )
                })
              }
              
              </div>
              <div className="panel-bottom">
                <form onSubmit={this.handleSubmit}>
                <textarea className="form-control" required ref={this.content}></textarea>
                <button type="submit" >评论</button>
                </form>
              </div>
          </div>
      </div>
            
        );
    }
}

export default App;
复制代码

效果:

到这里,咱们的代码实现的功能有,加一条评论,也能够删除一条评论。
在React中,视图是受到数据的驱动的,咱们最初定义的

state = {
        users: [],
        count: 0,
        id: 3
    }
复制代码

state中users的数据,会在componentDidMount生命周期时,获取到users列表,并经过this.setState({ users });方法更新视图。其余的操做,相似于handleSubmitremoveById一样都是经过操做state.users的数据达到增删的目的。

5.拆分组件

考虑到一个项目中的复杂度,咱们能够将上述App.js中的相关内容进行拆分为:列表组件List,列表项ListItemComment评论组件,这样,构造其余结构的时候,咱们就不用再去从新写一遍相同的代码。咱们在src文件夹下新建components文件夹,而且建立List.js ListItem.js Comment.js

  • 列表项组件:
import React, { Component } from 'react'
export default class ListItem extends Component {
    state = {
        users: [],
        id: 100000
    }
    addComment = (val) => {
        let id = this.state.id;
        let users = [...this.state.users, { avatar:  "http://05.imgmini.eastday.com/mobile/20171112/20171112104845_40c4a989ba5a02f512b05336bff309f8_1.jpeg", content: val, username: 'Jim', id: id}];
        this.setState({
            users
        });
        this.state.id+=1;
    }
    handleClick = (id) => {
        this.props.removeById(id);
    }
    removeById = (id) => {
        let users = this.state.users.filter(user=>user.id!==id); // 排除列表里相同id的,即达到删除的目的
        this.setState({
            users
        })
    }
  render() {
    let {id, avatar, content, username} = this.props;
    return (
        <div className="media">
                    <div className="media-left">
                        <img className="avatar" src={avatar} />
                    </div>
                    <div className="media-right">
                        <h3>{username} {id}</h3>
                        <div>评论:{content}</div>
                        <button className="btn btn-danger" onClick={(e)=>{
                            this.handleClick(id)
                        }}>删除</button>

                    </div>
                </div>
       
    )
  }
}

复制代码
  • 列表组件
import React, { Component } from 'react'
import ListItem from './ListItem'
export default class List extends Component {
  static props = {
    showComment: true
  }
  render() {
    return (
      <div>
        {
            this.props.users.map((user, index) => {
                return (
                    <ListItem showComment={this.props.showComment} {...user} key={index} removeById={this.props.removeById} addComment={this.props.addComment}></ListItem>
                )
            })
        }
      </div>
    )
  }
}

复制代码
  • 评论框组件
import React, { Component } from 'react'

export default class Comment extends Component {
    content = React.createRef();
    handleSubmit = (e) => {
        e.preventDefault();
        this.props.addComment(this.content.current.value);
    }
    render() {
        return (
            <form onSubmit={this.handleSubmit}>
            <textarea className="form-control" required ref={this.content}></textarea>
            <button type="submit" >评论</button>
            </form>
        )
    }
}

复制代码
  • App.js变为
import React, { Component } from 'react';
import axios from './request'
import 'bootstrap/dist/css/bootstrap.css'
import './Common/common.css'
import Comment from './components/Comment'
import List from './components/List'
import {Provider} from './context'

class App extends Component {
    state = {
        users: [],
        count: 0,
        id: 3
    }
    increment = () => {
        this.setState({
            count: this.state.count + 1
        })
    }
    addComment = (val) => {
        let id = this.state.id;
        let users = [...this.state.users, { avatar:  "http://05.imgmini.eastday.com/mobile/20171112/20171112104845_40c4a989ba5a02f512b05336bff309f8_1.jpeg", content: val, username: 'Jim', id: id}];
        this.setState({
            users
        });
        this.state.id+=1;
    }
    removeById = (id) => {
        console.log(id)

        let users = this.state.users.filter(user=>user.id!==id); // 排除列表里相同id的,即达到删除的目的
        this.setState({
            users
        })
    }
    async componentDidMount() {
        let users = await axios.get('/users.json');
        this.setState({
            users
        });
    }
    render() {
        return (
        <Provider value={{increment: this.increment}}>
            <div className="container">
                <div className="panel panel-danger">
                    <div className="panel-heading">
                        评论
                    </div>
                    <div className="panel-body">
                        <List users={this.state.users} showComment={true} removeById={this.removeById} addComment={this.addComment}></List>
                    </div>
                    <div className="panel-bottom">
                    
                    <br/>
                    <Comment addComment={this.addComment}></Comment>
                    得到的赞数量{this.state.count}
                    </div>
                </div>
            </div>
        </Provider>
            
        );
    }
}

export default App;
复制代码

看到这里,必定有疑问,那么咱们以前定义的users数据,removeByIdaddComment的方法,怎么用到组件上呢?下面咱们进行讲解。

6.组件间属性的传递

  • 组件的数据交互的方式是属性传递,传递属性值或方法
  • 子组件不能直接修改属性值
  • 可是能够经过父组件传递进来的方法调用以改变属性值
  • 数据传递是单向的:父->子,即常说的单项数据流
  • 子组件获取属性的方法:`this.props.fn
  • 可使用contextApi实现跨组件传递

上一节咱们拆分的组件中,在列表组件中本来的循环体数据源,由this.state.users改成了使用this.props.users,而在App.js中传入的方式为

<List users={this.state.users} showComment={true} removeById={this.removeById} addComment={this.addComment}></List>
复制代码

传入和获取是一一对应的。
一样,因为ListItem组件须要removeById方法,因此咱们从App.jsList组件就传入removeById,在List组件中调用ListItem时,再次传入ListItem,是一个父传子,子传孙的过程:

<ListItem showComment={this.props.showComment} {...user} key={index} removeById={this.props.removeById} addComment={this.props.addComment}></ListItem>
复制代码

ListItem组件中,咱们对removeById方法再包装一层

handleClick = (id) => {
        this.props.removeById(id);
}

...

<button className="btn btn-danger" onClick={(e)=>{
    this.removeById(user.id)
}}>删除</button>

复制代码

这里咱们的删除方法来自于根组件传递下来的方法,子组件获取后,对一样是传递进来的users进行修改,以到达改变数据的目的。以上就是简单的组件传值的讲解。

contextApi

若是咱们想给这个列表加一个点赞功能,即任何一个列表项组件均可以点赞,并且点赞还能够收集总数,这时候若是再去用父子间组件传值,可能代码实现起来会比较麻烦或者易错,由于涉及的层级不少。因此咱们利用contextApi来实现(react16.3)。

引入的方式(在例子中,我抽离了这个引入到context.js,就不用在每一个页面写一遍解构了):

import React from 'react'
let {Provider, Consumer} = React.createContext();

export {Provider, Consumer}
复制代码

在组件中的使用方法是,在父组件引入后,将父组件的返回值使用Provider包裹,并传入value属性:

import React, { Component } from 'react';
import {Provider} from './context'

class App extends Component {
    state = {
        users: [],
        count: 0,
        id: 3
    }
    // 点赞功能
    increment = () => {
        this.setState({
            count: this.state.count + 1
        })
    }
    render() {
        return (
        <Provider value={{increment: this.increment}}>
            <div className="container">
                <div className="panel panel-danger">
                    <div className="panel-heading">
                        评论
                    </div>
                    <div className="panel-body">
                        <List users={this.state.users} showComment={true} removeById={this.removeById} addComment={this.addComment}></List>
                    </div>
                    <div className="panel-bottom">
                    
                    <br/>
                    <Comment addComment={this.addComment}></Comment>
                    得到的赞数量{this.state.count}
                    </div>
                </div>
            </div>
        </Provider>
            
        );
    }
}

export default App;
复制代码

在子组件中,须要使用(消费)的返回值外层包裹Consumer,使用箭头函数传入value的值,即Provider传入的属性,便可在组件中直接调用父组件或更高阶的组件的传入属性。

import React, { Component } from 'react'
import {Consumer} from '../context'
...
export default class ListItem extends Component {
    ...
  render() {
    let {id, avatar, content, username} = this.props;
    return (
        <Consumer>
            {(value)=>{
                return <div className="media">
                    <div className="media-right">
                        ...
                        <button className="btn btn-primary" onClick={()=>{
                            value.increment()
                        }}>赞</button>
                        ...
                      </div>
                </div>
            }}
            
        </Consumer>
       
    )
  }
}

复制代码

总结

以上是我学习React入门的一些小总结,写了一个不太成熟的例子来练手,在表述上可能有一些跳跃还请见谅。这里附上这个小例子的Github代码,有须要详细了解的同窗能够看看:code。 但愿个人文章能帮到你。

相关文章
相关标签/搜索