本文主要写一些CRA脚手架的安装,React的语法,组件分类和组件传值等;若是您是已经在React上有丰富经验的开发者,欢迎指出文中有问题和能够改进的地方,对此我将表示感谢!
这是react的官方站点React官网javascript
本文将主要分为:css
阅读全文可能会花费您10-20分钟,若是以为有兴趣,能够一块儿敲敲代码
(使用编辑器:VSCODE,插件:VS Code ES7 React/Redux/React-Native/JS snippets
,这个插件能够快速构建组件格式,若是想要练练手的同窗就不要用快速指令了哦)html
First thing first,这里咱们利用create-react-app(须要nodejs环境)来建立这个项目,毕竟比较方便嘛,有其余建立项目和服务的方式也可使用。vue
create-react-app mycode
就能够建立一个文件夹为
mycode
的项目文件夹,注意哦,这个项目名称不支持大写字母
npm start
开启开发服务器,通常默认是3000端口,启动后会自动弹出localhost:3000
的页面npm run build
为生产环境建立打包的静态文件npm test
开启测试,这个我没有用过,有用过的同窗能够在评论里分享一下使用技术文章npm run eject
Eject 将全部的工具(配置文件和 package.json 依赖库)解压到应用所在的路径,这个过程是不可逆的那咱们开始吧,cd mycode
& npm start
。java
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中有什么,有多少层级,有多少组件,有多少逻辑,最终只有这一个入口。咱们将脚手架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,我习惯取做Apprender(<App></App>, window.root)
渲染组件到root容器,render
是react的核心渲染方法,后面咱们会一直用到按照上一节的引入各类库,咱们能够在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
用一句话来归纳就是: 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
若是咱们想在代码中写备注怎么办?是这样吗//
,哦不行,加个{//}
呢?哦不行,你在编辑器中能够看到
{/*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>
)
复制代码
页面:
let arr = [1,2,3];
//
let el = (
arr.map((item, key) => (
<li key={key}>{item}</li>
))
)
复制代码
class
→className
这个驼峰方式的写法代替了原生html的class,可是class仍是能够用的,脚手架会提示你这里应当使用className
for
→htmlFor
这个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的话:
可是若是咱们在某种状况下,必须使用一些平级元素怎么办呢,好比处于样式的考虑,咱们外层没有什么须要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的魅力吧。
在react项目中,基本上全部的结构功能均可以拆分红很细的一个个组件,好比一个页面上经常使用的菜单栏,能够拆分红:列表框List,列表项ListItem,列表连接Link等等,这样的好处是:
1.复用 2.方便维护 3.提升工做效率。
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)
复制代码
非受控组件:表单数据由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
,列表项ListItem
和Comment
评论组件,在实现的过程当中,咱们会讨论组件间数据传递的方式。
首先,咱们不拆分组件,将上述的例子简单构建出来,页面结构使用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;
复制代码
效果:
state = {
users: [],
count: 0,
id: 3
}
复制代码
state中users
的数据,会在componentDidMount
生命周期时,获取到users列表,并经过this.setState({ users });
方法更新视图。其余的操做,相似于handleSubmit
和removeById
一样都是经过操做state.users
的数据达到增删的目的。
考虑到一个项目中的复杂度,咱们能够将上述App.js中的相关内容进行拆分为:列表组件List
,列表项ListItem
和Comment
评论组件,这样,构造其余结构的时候,咱们就不用再去从新写一遍相同的代码。咱们在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>
)
}
}
复制代码
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
数据,removeById
和addComment
的方法,怎么用到组件上呢?下面咱们进行讲解。
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.js
的List
组件就传入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来实现(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。 但愿个人文章能帮到你。