学和使用react有一年多了,最近想在梳理一下react基础知识,夯实基础,激流勇进~ 关于reacr-router,redux,redux-saga后续都会慢慢输出,但愿各位看官老爷持续关注~~要是能给个赞鼓励一下就更赞了~react
提醒一下: 看完以后抓紧时间趁热打铁,redux,react-redux,redux-sagaredux
一篇文章总结redux、react-redux、redux-saga数组
一个JSX语法的示例,以下所示浏览器
const element = <h1>Hello, world!</h1>;
复制代码
这种语法形式,既不是HTML,也不是字符串,而是称之为JSX,是React里用来描述UI和样式的语法,JSX最终会被编译为合法的JS语句调用(编译器在遇到{
时采用JS语法进行解析,遇到<
就采用HTML规则进行解析)安全
JSX中,可使用花括号{}
嵌入任意的JavaScript合法表达式,如:2 + 2
、user.firstName
、formatName(user)
都是合法的。示例如:bash
const user = {
firstName: 'Zhang',
lastName : 'Busong'
};
const elem = (
<h1>Hello, {formatName(user)}</h1>
);
/*
这里的(),其实是可选的,可是React推荐加入(),这样子就会被视为一个表达式,而不会致使
自动插入分号的问题
*/
ReactDOM.render(
element,
document.getElementById('app')
)
复制代码
JSX自己也是一种表达式,因此它能够像其余表达式同样,用于给一个变量赋值、做为函数实参、做为函数返回值,等等。如:babel
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}</h1>
}
return <h1>Hello, Guest!</h1>;
}
复制代码
注意: 一、在JSX中,声明属性时不要使用引号,若是声明属性的时候使用引号,那么将被做为字符串解析,而不会被做为一个表达式解析,如:app
<div firstName="{user.firstName}" lastName={user.lastName}></div>
复制代码
解析后,能够获得:异步
<div firstName="{user.firstName}" lastName="Lau"></div>
复制代码
所以,当咱们须要使用一个字符串字面量的时候,可使用引号,可是若是要做为表达式解析的时候,则不该当使用引号 二、在JSX中,有些属性名称须要进行特殊处理。如class
应该用className
代替,tabindex
则用tabIndex
代替。这是由于JSX本质上更接近于JavaScript,而class
是JavaScript中的保留字。同时,应该使用camelCase
来命名一个属性,而不是使用HTML的属性命名方式 三、JSX自己已经作了防注入处理,对于那些不是明确编写的HTML代码,是不会被解析为HTML DOM的,ReactDOM会将他们一概视为字符串,在渲染完成前就转化为字符串,因此能够防止XSS攻击 四、若是JSX标签是闭合的,那么结尾须要用/>
,另外,JSX标签是能够互相嵌套的,这和HTML里是同样的编辑器
JSX经过babel编译,而babel实际上把JSX编译给React.createElement()
调用。以下JSX代码:
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
复制代码
是等同于如下的语句的:
const elem = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
复制代码
React.createElement()
方法会首先进行一些避免BUG的检查,而后返回相似如下例子的对象:
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world'
}
}
复制代码
这样的对象,则称为React元素
,表明全部呈如今屏幕上的东西。React正是经过读取这些对象来构建DOM,而且保持数据和UI同步的
元素(elements
)是构成React应用的最小单元,元素描述了想要在屏幕中看到的内容,如:
const element = <h1>Hello, world</h1>;
复制代码
和DOM元素不一样的是,React元素是纯对象,建立的代价低。而且React会进行优化处理,只把有必要的变化更新到DOM上。此外,元素和组件的概念,是不同的,组件是由元素组成的。
在React中,使用ReactDOM.render()
方法来将React元素渲染进一个DOM中。如:
ReactDOM.render(
element,
document.getElementById('root')
)
复制代码
React元素是不可变的,因此一旦一个元素建立完成后,咱们是没法改变其内容或者属性的。一个元素就像是动画里的一帧,它表明UI在某一时间点的样子。若是非要使用元素来构成可变化的UI界面,就须要使用setInterval
了,如:
function tick() {
const element = (
<div>Now is {new Date().toLocaleTimeString()}</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
复制代码
在实际开发中,大多数React应用只会调用一次ReactDOM.render()
,因此更好的方式是使用有状态组件
组件(component
)可以将UI划分为独立的、可复用的部分,这样咱们就只需专一于构建每个单独的部件。 从概念上看,组件就像是函数:接受任意的输入(称为属性,Props
),返回React元素。React中有两种定义组件的方式:函数定义
和类定义
这种方式是最简单的定义组件的方式,就像写一个JS函数同样,如:
function Welcome (props) {
return <h1>Hello, {props.name}</h1>;;
}
复制代码
还可使用ES6里的类来定义一个组件,以下所示:
class Welcome extends React.Component {
render () {
return <h1>Hello, {this.props.name}<h1>;
}
}
复制代码
这种方式比起函数定义
方式则更加灵活
先前,咱们遇到的React元素只是呈现一个DOM标签,如:
const element = <div />
复制代码
然而,React元素也能够是用户自定义的组件
,如:
const element = <Welcome name="Tom" />
复制代码
Welcome组件中声明了一个属性name="Tom"
,而这个属性,将以props.name
的方式传递给组件,以下方式:
function Welcome (props) {
return <h1>Hello, {props.name}</h1>;
}
复制代码
此时,对于如下的代码:
ReactDOM.render(
<Welcome name="张不怂" />,
document.getElementById('root')
)
复制代码
最终就会以<h1>Hello, 张不怂</h1>
的方式呈现。在这个过程当中,发生了以下的事情:
<Welcome name="张不怂" />
元素调用了ReactDOM.render()
丰富{ name: '张不怂' }
做为props实参来调用Welcome组件<h1>Hello, 张不怂</h1>
元素组件是能够组合的。即组件内部能够引用其余组件,如:
function Welcome (props) {
return <h1>Hello, {props.name}</h1>;
}
function App () {
return (
<div>
<Welcome name="Tom" />
<Welcome name="Jack" />
<Welcome name="Mike" />
</div>
)
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
复制代码
注意: 在React中,组件必须返回单一
的根元素,这也是为何App组件中须要用<div>
标签包裹的缘由。如如下的方式,是错误的(由于它有3个根元素):
function App () {
return (
<Welcome name="Tom" />
<Welcome name="Jack" />
<Welcome name="Mike" />
)
}
复制代码
考虑如下这种状况:
function sum (a, b) {
return a + b;
}
复制代码
这种函数称为纯函数
:它不改变本身的输入值,且老是对相同的输入返回相同的结果。 与之对立的,则是非纯函数
,如:
function withdraw (account, amount) {
account.total -= amount;
}
复制代码
非纯函数
在函数内改变了输入的参数。在React中,不管是经过function
仍是class
声明组件,咱们都不该该修改它自身的属性(props
)。虽然React至关灵活,可是它也有一个严格的规定:全部的React组件都必须像纯函数那样来使用它们的props
使用类定义组件
有一些额外的好处,如拥有本地状态
这一特性。 如下是一个类定义组件
class Clock extends React.Component {
render () {
return (
<div>
<h1>Hello, world!</h1>
<h2>Now is {this.props.date.toLocaleTimeString()}</h2>
</div>
);
}
}
复制代码
须要注意的有:
类名
即为组件名
(不管是函数定义组件仍是类定义组件,组件名称的首字母都必须大写,而且继承自React.Component
)render()
方法,用来返回须要呈现的内容state是属于一个组件自身的。咱们能够在类的构造函数constructor
中来初始化状态,如:
constructor (props) {
super(props)
this.state = {
date: new Date()
}
}
复制代码
如此一来,咱们就能够在render()
函数中使用this.state.xxx
来引用一个状态
在应用里,每每都会有许许多多的组件。在组件销毁后,回收和释放它们所占据的资源很是重要。 在时钟应用
的例子里,咱们须要在第一次渲染到DOM的时候设置一个定时器,而且须要在相应的DOM销毁后,清除这个定时器。那么,这种状况下,React为咱们提供了生命周期的钩子函数,方便咱们进行使用。在React中,生命周期分为: 1)Mount
已插入真实DOM 2)Update
正在从新渲染 3)Unmount
已移出真实DOM 而相应的,生命周期钩子函数有:
componentWillMount
componentDidMount
componentWillUpdate(newProps, nextState)
componentDidUpdate(prevProps, prevState)
componentWillUnmount()
此外,还有两种特殊状态的处理函数:
componentWillReceiveProps(nextProps)
已加载的组件收到新的参数时调动shouldComponentUpdate(nextProps, nextState)
组件判断是否从新渲染时调用所以,基于生命周期钩子函数,咱们能够实现一个时钟应用以下:
class Clock extends React.Component {
constructor (props) {
super(props);
this.state = {
date: new Date()
}
}
tick () {
this.setState({
date: new Date()
});
}
componentDidMount () {
this.timerId = setInterval(() => {
this.tick()
}, 1000);
}
componentWillUnmount () {
clearInterval(this.timerId);
}
render () {
return (
<div>Now is {this.state.date.toLocaleTimeString()}</div>
);
}
}
复制代码
须要注意的是: 1)render()
里用不到的state
,不该该声明在state
里 2)不能直接使用this.state.xxx = xxx
的方式来改变一个state
的值,应该使用this.setState()
。如:
setName () {
this.setState({
name: '张不怂'
})
}
复制代码
this.setState()
会自动覆盖this.state
里相应的属性,并触发render()
从新渲染。 3)状态更新多是异步的 React能够将多个setState()
调用合并成一个调用来提高性能。且因为this.props
和this.state
多是异步更新的,因此不该该依靠它们的值来计算下一个状态。这种状况下,能够给setState
传入一个函数,如:
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
复制代码
React元素的事件与DOM元素相似,不过也有一些区别,如: 1)React事件使用camelCase
命名(onClick
),而不是全小写的形式(onclick
) 2)使用JSX,传入的是事件的句柄,而不是一个字符串 如如下的HTML:
<button onclick="increment()">ADD</button>
复制代码
使用React的方式描述如:
<button onClick={increment}>ADD</button>
复制代码
还有一个不一样在于,在原生DOM中,咱们能够经过返回false
来阻止默认行为,可是这在React中是行不通的,在React中须要明确使用preventDefault()
来阻止默认行为。如:
function ActionLink () {
function handleClick (e) {
e.preventDefault();
alert('Hello, world!');
}
return (
<a href="#" onClick={handleClick}>Click Me</a>
);
}
复制代码
这里,事件回调函数里的event
是通过React特殊处理过的(遵循W3C标准),因此咱们能够放心地使用它,而不用担忧跨浏览器的兼容性问题。 注意: 在使用事件回调函数的时候,咱们须要特别注意this
的指向问题,由于在React里,除了构造函数和生命周期钩子函数里会自动绑定this为当前组件外,其余的都不会自动绑定this
的指向为当前组件,所以须要咱们本身注意好this的绑定问题, 一般而言,在一个类方式声明的组件里使用事件回调,咱们需要在组件的constructor
里绑定回调方法的this指向,如:
class Counter extends React.Component {
constructor (props) {
super(props);
this.state = {
counter: 0
}
// 在这里绑定指向
this.increment = this.increment.bind(this);
}
increment () {
this.setState({
counter: this.state.counter + 1
});
}
render () {
return (
<div>
The counter now is: {this.state.counter}
<button onClick={this.increment}>+1</button>
</div>
);
}
}
复制代码
固然,咱们还有另一种方法来使用箭头函数绑定指向,就是使用实验性
的属性初始化语法,如:
class Counter extends React.Component {
increment: () => {
this.setState({
counter: this.state.counter + 1
});
}
// ...
}
复制代码
3)像事件处理程序传递参数 咱们能够为事件处理程序传递额外的参数,方式有如下两种:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
复制代码
须要注意的是,使用箭头函数的状况下,参数e
要显式传递,而使用bind的状况下,则无需显式传递(参数e
会做为最后一个参数传递给事件处理程序)
在React里,咱们能够建立不一样的组件来封装咱们须要的功能。咱们也能够根据组件的状态,只渲染组件中的一部份内容,而条件渲染就是为此而准备的。在React中,咱们能够像在JavaScript中写条件语句同样地写条件渲染语句,如:
function Greet(props) {
const isLogined = props.isLogined;
if (isLogined) {
return <div>Hello !</div>;
}
return <div>Please sign in</div>;
}
ReactDOM.render(
<Greet isLogined={true} />,
document.getElementById('root')
);
复制代码
这将渲染出:
<div>Hello !</div>
复制代码
咱们也可使用变量来存储元素,如:
function LogBtn(props) {
var button;
const isLogined = props.isLogined;
if (isLogined) {
button = <button>退出</button>
} else {
button = <button>登录</button>
}
return <div>You can {button}</div>;
}
ReactDOM.render(
<LogBtn isLogined={false} />,
document.getElementById('root')
);
复制代码
因为JavaScript语法对待&&
运算符的性质,咱们也可使用&&运算符来完成条件渲染,如:
function LogBtn(props) {
var button;
const isLogined = props.isLogined;
return (
<div>Hello
{!isLogined && (
<button>请登录</button>
)}
</div>
)
}
复制代码
当props.isLogined
为false的时候,就会渲染出:
<div>Hello <button>请登陆</button></div>
复制代码
咱们可能已经发现了,其实JSX能够像一个表达式那样子灵活使用,因此,咱们天然也可使用三目运算符进行渲染,如:
function LogBtn (props) {
const isLogined = props.isLogined;
return (
<div>You can
<button>{isLogined ? '退出' : '登录'}</button>
</div>
)
}
复制代码
有时候,咱们但愿是整个组件都不渲染,而不只仅是局部不渲染,那么这种状况下,咱们就能够在render()
函数里返回一个null
,来实现咱们想要的效果,如:
function LogBtn (props) {
const isLogined = props.isLogined;
const isShow = props.isShow;
if (isShow) {
return (
<div>You can
<button>{isLogined ? '退出' : '登录'}</button>
</div>
)
}
return null;
}
复制代码
注意: 组件里返回null
不会影响组件生命周期的触发,如componentWillUpdate
和componentDidUpdate
仍然会被调用
在JavaScript中,咱们可使用map()
函数来对一个数组列表进行操做,如:
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(number => number*2);
console.log(doubled); // 获得[2, 4, 6, 8, 10]
复制代码
一样的,在React里,咱们也可使用map()
来进行列表渲染,如:
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map(number => {
return (
<li>{number}</li>
)
});
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
)
复制代码
这将获得:
<ul><li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
复制代码
固然,咱们还能够进行更好的封装,如:
function NumberList (props) {
const numbers = props.numbers;
const listItems = numbers.map(number => {
return (
<li>{number}</li>
)
});
return <ul>{listItems}</ul>
}
复制代码
当咱们运行以上的代码的时候,会发现控制台提示:Each child in an array or iterator should have a unique "key" prop
,所以,咱们须要为列表项的每个项分配一个key
,来解决这个问题,一般而言,咱们可使用如下几种方式来提供key
:
<li key={item.itemId}>
index
),如:const listItems = numbers.map((number, index) => {
<li key={index}>{number}</li>
});
复制代码
可是React不推荐在须要从新排序的列表里使用索引下标,由于会致使变得很慢。
注意: 只有在一个项的同胞里区分彼此的时候,才须要使用到key,key不须要全局惟一,只须要在一个数组内部区分彼此时惟一即可。key的做用是给React一个提示,而不会传递给组件。若是咱们在组件内须要一样的一个值,能够换个名字传递,如:
const content = posts.map(post => (
<Post key={post.id} id={post.id} title={post.title} />
));
复制代码
表单和其余的React中的DOM元素有所不一样,由于表单元素生来就是为了保存一些内部状态。在React中,表单和HTML中的表单略有不一样
HTML中,<input>
、<textarea>
、<select>
这类表单元素会维持自身状态,并根据用户输入进行更新。不过React中,可变的状态一般保存在组件的this.state
中,且只能用setState()
方法进行更新,如:
class NameForm extends React.Component {
constructor (props) {
super(props);
this.state = {
value: ''
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange (event) {
this.setState({
value: event.target.value
});
}
handleSubmit (event) {
alert('Your name is '+this.state.value);
event.preventDefault();
}
render () {
return (
<form onSubmit={this.handleSubmit}>
Name: <input type="text" value={this.state.value} onChange={this.handleChange} />
<input type="submit" value="Submit" />
</form>
)
}
}
复制代码
和HTML中不一样的是,React中的textarea
并不须要写成<textarea></textarea>
的形式,而是写成<textarea value="" ... />
的形式即可。而对于HTML中的select
标签,一般作法是:
<select>
<option value="A">A</option>
<option value="B" selected>B</option>
<option value="C">C</option>
</select>
复制代码
可是React中,不须要在须要选中的option
处加入selected
,而只须要传入一个value,就会自动根据value来选中相应的选项,如:
<select value="C">
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
</select>
复制代码
那么如上述例子,C所在的这个option
就会被选中
一般一个表单都有多个输入,若是咱们为每个输入添加处理事件,那么将会很是繁琐。好的一个解决办法是,使用name,而后根据event.target.name
来选择作什么。如:
class Form extends React.Component {
constructor (props) {
super(props);
this.state = {
name: '',
gender: '男',
attend: false,
profile: ''
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleInputChange (event) {
const target = event.target;
const value = target.type==='checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
handleSubmit (event) {
this.setState({
profile: `姓名:${this.state.name},${this.state.gender},${this.state.attend ? '参加' : '不参加'}活动`
});
event.preventDefault();
}
render () {
return (
<form>
<p>姓名:<input name="name" value={this.state.name} onChange={this.handleInputChange} /></p>
<p>性别:
<select name="gender" value={this.state.gender} onChange={this.handleInputChange}>
<option value="男">男</option>
<option value="女">女</option>
</select>
</p>
<p>是否参加:<input name="attend" type="checkbox" onChange={this.handleInputChange} checked={this.state.attend} /></p>
<input type="submit" value="Submit" onClick={this.handleSubmit} />
<p>您的报名信息:{this.state.profile}</p>
</form>
)
}
}
复制代码
大多数状况下,使用受控组件
实现表单是首选,在受控组件中,表单数据是交由React组件处理的。若是想要让表单数据由DOM处理(即数据不保存在React的状态里,而是保存在DOM中),那么可使用非受控组件
,使用非受控组件
,能够无需为每一个状态更新编写事件处理程序,使用ref
便可实现,如:
class NameForm extends React.Component {
constrcutor(props) {
super(props)
}
handleSubmit(event) {
console.log('A name was submitted: ', this.input.value)
event.preventDefault()
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name: <input type="text" ref={input => this.input = input} />
</label>
<input type="submit" value="submit" />
</form>
)
}
}
复制代码
对于非受控组件
,若是要指定默认值,那么可使用defaultValue
,如:
<input type="text" defaultValue="Hello" ref={input => this.input = input} />
复制代码
相应的,type="checkbox"
和type="radio"
,则使用defaultChecked
当须要几个组件共用状态数据的时候,可使用状态提高技术。核心思想在于:把数据抽离到最近的共同父组件,父组件管理状态(state),而后经过属性(props)传递给子组件。如实现一个货币转换的组件,能够以下:
function USD2RMB (amount) {
return amount * 6.7925;
}
function RMB2USD (amount) {
return amount * 0.1472;
}
function convert (amount, typeFn) {
return typeFn(amount);
}
复制代码
咱们但愿在RMB的输入表单上上输入的时候,USD的输入表单上的数值也同步更新,这种状况下,若是RMB组件本身管理本身的状态,是很难以实现的,所以,咱们须要让这个状态提高自父组件进行管理。以下:
class CurrencyInput extends React.Component {
constructor (props) {
super(props)
this.handleChange = this.handleChange.bind(this)
}
handleChange (event) {
this.props.onInputChange(event.target.value)
}
render () {
const value = this.props.value
const type = this.props.type
return (
<p>{type}: <input type="text" value={value} onChange={this.handleChange} /></p>
);
}
}
复制代码
最后定义一个共同的父组件,以下:
class CurrencyConvert extends Component {
constructor (props) {
super(props);
this.state = {
type: 'RMB',
amount: 0
}
this.handleRMBChange = this.handleRMBChange.bind(this);
this.handleUSDChange = this.handleUSDChange.bind(this);
}
handleRMBChange (amount) {
this.setState({
type: 'RMB',
amount
});
}
handleUSDChange (amount) {
this.setState({
type: 'USD',
amount
});
}
render () {
const type = this.state.type;
const amount = this.state.amount;
const RMB = type==='RMB' ? amount : convert(amount, USB2RMB);
const USD = type==='USD' ? amount : convert(amount, RMB2USB);
return (
<div>
<p>Please Input:</p>
<CurrencyInput type="RMB" value={RMB} onInputChange={this.handleRMBChange} />
<CurrencyInput type="USD" value={USD} onInputChange={this.handleUSDChange} />
</div>
);
}
}
复制代码
React推崇更多的是使用组合,而非使用继承。对于一些使用场景,React给出的建议以下:
当父组件不知道子组件可能的内容是什么的时候,可使用props.children
,如:
function Article (props) {
return (
<section>
<aside>侧边栏</aside>
<article>{props.children}</article>
</section>
);
}
function App () {
return (
<Article>这是一篇文章</Article>
);
}
复制代码
这将渲染获得:
<section>
<aside>侧边栏</aside>
<article>这是一篇文章</article>
</section>
复制代码
咱们还能够自定义名称,由于JSX实际上会被转化为合法的JS表达式,因此,还能够有:
function Article (props) {
return (
<section>
<aside>{props.aside}</aside>
<article>{props.children}</article>
</section>
);
}
function App () {
return (
<Article aside={
<h1>这是一个侧栏</h1>
}>这是一篇文章</Article>
);
}
复制代码
这将渲染获得:
<section>
<aside><h1>这是一个侧栏</h1></aside>
<article>这是一篇文章</article>
</section>
复制代码
在Facebook的网站上,使用了数以千计的组件,可是实践证实尚未发现须要使用继承才能解决的状况。 属性和组合为咱们提供了清晰的、安全的方式来自定义组件的样式和行为,组件能够接受任意元素,包括:基本数据类型、React元素、函数。 若是要在组件之间复用UI无关的功能,那么应该将其提取到单独的JavaScript模块中,这样子能够在不对组件进行扩展的前提下导入并使用函数、对象、类
以为对你有帮助,不妨点个
不妨再点个关注,不迷路,下一篇关于redux的明天就发!!~