浅扒react
简介:
当2014年Facebook推出React时,给整个业界带来全新的看待网页应用开发的方式,和React一同问世的Flux,也克服传统MVC框架的很多弊病。技术在不断发展,在2015年,Flux的一个变体Redux出现,进一步优化了Flux的功能。
优秀之处:
1、 专注视图层:(不处理理由,数据,以及逻辑的处理)
2、 VirtualDom:只对dom更新,不对dom更新;只对dom进行必要的更新,实现的重绘的最小化;
3、 jsx语法:
1) 高内聚,低耦合
2) onClick 不是原生dom中的onclick事件,这里是onClict是通过react的事件委托方式,触发挂在顶层dom节点上的一个事件处理函数(提高了性能);
3) 可以将样式一起写入组件文件中;
4) 关于jsx语法的说法:
在jsx语法中可以直接写入组件,注意组件为了和普通标签区别,必须第一个字母大写;
jsx语法是历史的进步还是倒退;(样式,结构,逻辑于一体)
4、 活跃的生态圈:(开源项目)
5、 组件式开发
一、 手动搭建项目:
1、npm init
2、安装项目依赖
cnpm install --save-dev babel-core babel-loader babel-preset-stage-2babel-plugin-transform-react-jsx babel-plugin-transform-runtime babel-preset-es2015 babel-preset-react babel-preset-stage-0 css-loader html-loader html-webpack-plugin react react-dom style-loader url-loader webpack webpack-dev-server(还有一些依赖包可以依照需要下载)
3、配置babelrc文件:
{
"presets": [
"es2015",
"stage-0",
"react"
],
"plugins": ["transform-runtime", "transform-react-jsx"]
}
4、webpack.config.js的配置文件:
除了如下配置有更改,其他可以完全遵照vue的配置
{
test: /\.js|.jsx$/,
use: [
"babel-loader",
],
exclude: /node_modules/
},
5、在vsCode中开发react的配置:
安装jsx插件配置如下:
安装React/Redux/react-router Snippets插件,可以帮助我们快速生成一定的语句;
所有的react组件使用jsx后缀来标识;
二、 自动化构建项目:
1、初始化:
全局安装:
npm install –-global create- react- app
初始化项目:
create- react- app myReactApp
启动服务:
cd myReactApp
npm start
2、组件写法:
importReact,{Component} from "react"
importClickCount from "./ClickCount.jsx"
class Main extends Component {
constructor(props){
super(props)
}
render(){
return (
<ul>
<li><ClickCount name="first" num={1}></ClickCount></li>
<li><ClickCount name="second" num={10}></ClickCount></li>
<li><ClickCount name="third" num={7}></ClickCount></li>
</ul>
)
}
}
export defaultMain
注:对于jsx语法的解析必须引入React,虽然这里不用,但是如果不引入会因无法解析jsx而报错;组件的定义必须遵循首字母大写
3、jsx语法使用:
属性:
1、属性值直接为字符串
2、动态解析属性值
constelement=(<imgsrc={user.avatarUrl}></img>);
react的jsx:
constelement=(
<h1className="greeting">
Hello,world!
</h1>
);
用React.createElement()同样可以达到同样的效果;
constelement=React.createElement(
'h1',
{className:'greeting'},
'Hello, world!'
);
4、项目的分解:
npm run eject 进行项目配置文件的弹射;
5、react的工作方式:
1) 从繁琐的dom处理中解脱出来,响应式,函数式编程思维;输入相同的数据,将产出相同的效果;数据更新,dom同时做出更新;
2) Virtual Dom
Dom:结构化文本的抽象表达式;
虚拟dom:对dom树的抽象,不会触及浏览器,只存在于js内存的空间树形结构,每次的数据刷新,render函数的执行都会触发一个新旧虚拟dom的对比,如果无改变,则不更新,有改变,只更新相应dom结构;
三、 元素的渲染
a) jsx语法将元素渲染到dom中
b) 更新元素渲染:
React 元素都是immutable 不可变的。当元素被创建之后,你是无法改变其内容或属性的;更新界面的唯一方法就是重新挂载节点
function tick() {
const element = (
<h2>{newDate().toLocaleTimeString()}.</h2>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
c) 只会更新必要的部分(可以在谷歌开发者工具中进行dom更新的查看)
d) 在react的jsx语法中,如果标签没有子元素,可以直接使用闭合标签;
e) 在react的组件必须首字母进行大写,否则React.createEelement则无法查找到组件,进行编译;
f) jsx支持标签名为表达式;,如果使用动态标签名,则需先将表达式解析赋值给一个变量;
四、 React组件以及基本语法:
1、 组件的设计要素:
高内聚:把逻辑紧密相关的内容放在一个组件中;
低耦合:不同组件之间的依赖关系要尽量弱化;尽量保持每个组件的独立性;
2、 组件的生命周期:
1) 装载过程:
a) Constructor:
初始化state;
绑定成员函数的this环境(bind、:: )防止以解构的方式进行全局作用域进行调用;
例:this.fn=this.fn.bind(this)或this.fn=::this.fn
b) GetInitialState:初始化state值,必须结合React.createClass()使用,使用es6的方式,此函数无效果;
const Test=React.createClass({
getInitialState:function(){
return {
name:"zhangsan"
}
},
getDefaultProps:function(){
return {
name:"zhangsan"//这里的zhangsan相当于默认值
}
}
})
c) GetDefaultProps:初始化props值,必须结合React.createClass()使用,使用es6的方式,此函数无效果,用属性defaultProps代替;
class Test extends Component {
constructor(props){
super(props)
this.state={data:new Date()}
}
render(){
return (
<ul>
<li><ClickCount name="third" num={7}></ClickCount></li>
</ul>
)
}
}
Test.defaultProps={
return {
name:"moren"
}
}
d) ComponentWillMount:在render函数调用之前进行调用;
e) Render:并不做实际的渲染动作,只返回一个jsx描述结构,最后渲染与否由react来决定;必须是个纯函数,不能设计到数据的更变(this.setState);
render(){
return (
<ul>
<li><ClickCount name="third" num={7}></ClickCount></li>
</ul>
)
}
f) ComponentDidMount:在render函数调用之后进行调用;但不是在render函数调用之后立即调用,而是当所有的dom树挂载并渲染完成后才会调用,这是因为render函数并不进行渲染,而只是返回一个jsx对象,渲染的工作由react库进行,只有当所有的组件的jsx对象组合完毕之后,react通过对比后才会渲染,所有此钩子函数是发生的所有组件的render函数都执行后才会执行;(只能在浏览器端触发)
注:与angular中的link或者post函数有点相似,但是这里不仅指子组件,而是当前组件中的所有组件,包括兄弟组件;提供了dom操作的接口
2) 更新过程:
a) ComponentWillReceiveProps:传入的props的改变或者组件进行刷新(forceUpdate函数触发组件的刷新)都会触发此函数,但是通过this.setState改变的数据则不会触发此函数,
b) ShouldComponentUpdate:react组件的钩子函数两个需要有返回值的钩子函数之一,另一个为render,此钩子函数的返回值为一个bolen值,如果为true时,则prop的改变以及state的改变都会引起组件的刷新,如果为false时,则不再进行渲染;此钩子函数接受两个参数,一个是nextProps,一个是nextState,可以将将要更新的值和此时的做对比,然后返回true和false来进行性能的校优;
c) ComponentWillUpdate:跟componentWillMount相似
d) Render
e) ComponentDidUpdate:跟componentDidMount相似
3) 卸载过程:
ComponentWillUnmount:此钩子函数可以在组件卸载前执行,可以进行手动添加dom元素的删除,以及计时器和事件监听的移除;
五、 React组件的数据
组件内部数据类型:
两种数据结构,prop和state,这两种数据的改变都会引起组件的重新渲染;
1、 Prop:组件的外部接口,接受外部数据;跟html的属性的书写类似,但是除了接受字符串之外,还可以接受js对象,数字等的;如果组件进行数据反馈给外界,可以通过prop传入组件一个函数;
Prop的读取:
由class类的constructor函数接受,以及super()方法调用时的传入;
最后的props为一个对象,键值分别为传入时的属性名;这里比较方便的是使用es6的解构赋值;
PropTypes的检查:
可以通过增加类的propTypes属性来定义prop规格,在constructor函数中进行如下定义:
This.propTypes={
caption:PropTypes.string.isRequired,
initValue:PropTypes.number
}
注意:此属性不会影响组件的渲染,只是做到在开发过程中辅助开发;
2、 State:由于组件不能改变出入的prop,所以当组件要进行自己的状态纪录时就需要用到state;
初始化:state的初始化可以在constructor中,通过this.state进行设置,值必须为一个js对象的格式,通常将prop传入的外部值赋给state,以便后续操作;设置默认值时,可以用 ||操作符进行;
读取和更新state
更新使用this.setState({count:this.state.count++})方法;
读取为:this.state.count这种方式
3、 Prop和state的对比:
Prop:定义外部接口;赋值在外部环境使用组件时;组件内部不能更改
State:纪录内部状态;赋值在组件内部;在组件内部可以进行更改;
六、 react中css的样式书写
1、 全局引入css文件
此种方式会将css文件在全局引入,也就是说当前样式文件中书写的所有样式,在任何一个组件都可以引用,此种引用方式有可能会造成全局的污染,变量名冲突,又或者说不符合组件式开发的思想;所以此种方式一般是引入全局的css文件;
i mport “app.css”
2、 局部作用域(css modules的方式)
此种方式采用的是css modules的做法,通过此做法,最后会将css文件中类名和dom中类名形成唯一的hash值,这样就不会造成类名的冲突了,使用场景,一般为组件内部的样式防止全局书写造成的类名污染;
比如如下写法:
app.css:
.title {
color: red;
}
jsx:
import style from './App.css';
export default ()=>{
return(
<h1 className={style.title}>
Hello World
</h1>
);
};
最后会编译成如下代码:
<h1 class="_3zyde4l1yATCOkgn-DBWEL">
Hello World
</h1>
._3zyde4l1yATCOkgn-DBWEL {
color: red;
}
注:在css Modules中还提供了一种css全局作用域的书写方式,也就是说如果通过此种方式注册的css类名不会被替换为hash值,书写方式如下:
css:
:global(.title){
color:red;
}
3、 行内样式:
在jsx的dom上直接使用style属性进行样式的设置,样式的值为一个对象,这种方式的书写,可以支持css样式以js对象的方式进行书写,如果不想将组件的内部样式进行作用域隔离使用cssmodules的方式进行书写,则可以此种方式进行书写,可以直接在render函数中定义样式对象,也可以通过专门外部样式js文件配置样式对象,然后引入到dom进行使用,书写方式如下:
varstyle={
color:red;
}
<divstyle={style}>天气真好</div>
注:在书写css属性时,一定要采用驼峰式写法;
七、 事件处理
1、 react中的事件绑定属性采用的是驼峰式写法:
<button onClick={this.clickfn}>点击</button>
2、 处理react使用class类定义方法时,无法自动绑定this的弊端:
在constructor中使用bind为事件函数绑定this
this.clickfn=this.clickfn.bind(this)
在dom中调用函数时,先使用箭头函数绑定this,箭头函数的this是在定义时形成的
<button onClick={()=>this.clickfn()}>点击</button>
3、 jsx的onClick和html的onclick不同之处
html的onclick弊端:
注册的事件处理函数都是全局环境,污染了全局环境;
使用onclick的dom元素,如果在dom树中删除时,必须手动的注销事件处理器,否则会造成内容泄露;
jsx的onClick:
挂载的每一个函数都是在组件内部,而不是全局;
无论多少个onClick都是采用的事件委托的方式,在dom树顶层添加一个事件监听函数,此函数会根据具体组件分配具体的函数;
八、 条件判断:
在react的jsx语法中,如果遇到js语句使用{}进行包裹;
1、 if语句的使用:
render() {
var element=null
if(this.state.stateT){
element= <div > stateT为true时< /div>
}else{
element= <div > stateT为false时< /div>
}
return (
<div>
<button onClick={this.changeFn}>切换</button>
{element}
</div>
)
}
2、 jsx与运算符&&的结合:
render(){
return (
<div>
<button onClick={this.changeFn}>切换</button>
{this.state.stateT&&<div > stateT为false时< /div>}
</div>
)
}
3、 三目运算符
render(){
return (
<div>
<button onClick={this.changeFn}>切换</button>
{this.state.stateT?<div>stateT为true时</div>:<div> stateT为false时</div>}
</div>
)
}
4、 在jsx语法中,render可以返回null,表示不进行任何dom的渲染,但是钩子函数会执行;
九、 列表和keys
1、 基础用法:(key一般采用数据id,或者数据在数组中的索引)
render() {
var element=this.state.list.map((i,index)=><li key={index}>{i}</li>)
return (
<div>
<button onClick={this.changeFn}>切换</button>
{element}
</div>
)
}
注:如果后续进行数据的重新排序,则最好不要使用索引当作key值,否则,由于key值和dom的内容同时改变,则会引起全部dom节点的重绘;如果使用的是id,则只会调整dom节点的顺序,不会引起重绘dom树;(可以用上面例子演示,推荐使用id作为标识,而不是index)
2、 key值只需要在兄弟元素之间唯一就可以,不需要是在全局唯一的
render() {
var element=this.state.list.map((i,index)=><li key={i}>{i}</li>)
var element2=this.state.list.map((i,index)=><li key={i}>{i+1}</li>)
return (
<div>
<button onClick={this.changeFn}>切换</button>
{element}
{element2}
</div>
)
}
3、 在jsx中使用map进行遍历
render() {
return (
<ul>
<button onClick={this.changeFn}>切换</button>
{this.state.list.map((i)=><li key={i}>{i}</li>)}
</ul>
)
}
十、 表单
在vue中,我们知道由于vue的mvvm架构,v与m之间是响应式的,可以实时同步,但是在react中不存在数据的双向数据绑定,只是负责view层的渲染,所以我们在react中取表单中的值时需要一定的方式
1、 受控组件(采用react的单向数据流方式实现vue一样的双向数据绑定)
changeFn(event){
this.setState({
name:event.target.value
})
}
render() {
return (
<div>
<p>表单</p>
<label htmlFor="name">name:</label>
<input type="text" id="name" value={this.state.name} onChange={this.changeFn}/>
<p>{this.state.name}</p>
</div>
)
}
如果想对从input中获取到的值进行格式化时,则如下操作就可以实现:
this.setState({
name:event.target.value.toUpperCase()
})
2、 textarea标签在react中设置和读取值都采用value的方式
3、 在react中的使用select,并实时获取其选择的值:
render() {
return (
<div>
<p>表单</p>
<label htmlFor="name">name:</label>
<select value={this.state.name} onChange={this.changeFn}>
<option value="one">one</option>
<option value="two">two</option>
<option value="three">three</option>
</select>
<p>{this.state.name}</p>
</div>
)
}
4、 在react中实现多表单元素值得获取
changeFnAll(event){
let target=event.target
let name=target.name
let value=target.type==="checkbox"?target.checked:target.value
this.setState({
[name]:value
})
}
// 实现多表单元素值的获取
render() {
return (
<div>
<p>表单</p>
<input type="checkbox" value={this.state.checkbox} onChange={this.changeFnAll} name="checkbox"/>
<input type="text" value={this.state.text} onChange={this.changeFnAll} name="text"/>
<p>{this.state.checkbox.toString()}</p>
<p>{this.state.text}</p>
</div>
)
}
十一、 组件间通讯
1、 父组件向子组件传递数据:
a) 字符串常量的传递:
<MyComponentmessage="hello world"/>
<MyComponentmessage={'hello world'}/>
b) 没有给属性传值,它默认为 true
<MyTextBoxautocomplete/>
不建议如下写法,容易与es6的对象简洁表示法冲突
<MyTextBoxautocomplete={true}/>
c) 使用扩张运算符进行属性的设置
constprops={firstName:'Ben',lastName:'Hector'};
<Greeting {...props} />;
2、 子组件向父组件传递数据
只能通过将父组件的一个方法用prop的方式传入子组件,在子组件触发更变时,调用这个传入的方法,然后把子组件更变后的值以参数的形式传入父组件;从而达到父组件和子组件的通信的效果;
3、 兄弟组件间的通讯
a) 状态提升
对于没有直接关联关系的两个节点,就如 Child_1 与 Child_2 之间的关系,他们唯一的关联点,就是拥有相同的父组件。参考之前介绍的两种关系的通讯方式,如果我们向由 Child_1 向 Child_2 进行通讯,我们可以先通过 Child_1 向 Parent 组件进行通讯,再由 Parent 向 Child_2 组件进行通讯
b) 观察者模式
也就是像angular和vue的事件派发机制,在这里react没有内置的事件派发模式,所以需要手动封装一个事件派发机制;
如下代码:
var watchObj = {
arrFn: {},
on(name, fn) {
if (this.arrFn[name] === undefined) {
this.arrFn[name] = []
}
this.arrFn[name].push(fn)
},
emit() {
let name, args;
if (arguments.length == 0) {
returnfalse;
}
name = arguments[0]
args = [].concat(Array.prototype.slice.call(arguments, 1));
if (this.arrFn[name] !== undefined&&this.arrFn[name].length >0) {
this.arrFn[name].forEach(function(i) {
i(...args)
}, this);
}
}
}
exportdefault watchObj
十二、 组合与继承
在vue中的有一种组件特性叫内容分发,可以在父组件中自定义子组件的dom结构,在react中也提供了这么一种机制
1、 包含关系:(在子组件通过this.props.children获取父组件中写在子组件开闭标签间的dom节点)
父组件:
render() {
return (
<div>
<Children>
<p>相当于vue的内容分发</p>
</Children>
</div>
)
}
子组件:
render() {
return (
<div>
<p>这里是子组件</p>
{this.props.children}
</div>
)
}
2、 子组件中动态渲染其他组件:(在父组件中将其他组件动态绑定到prop属性上,在子组件中通过相应的属性来获取)
父组件:
render() {
return (
var one= <span>这里为true</span>
var two=<span>这里为false</span>
return (
<div>
<Slot one={one} two={two}></Slot>
</div>
)
)
}
子组件:
render() {
return(
<div>
<p>这里是子组件</p>
{this.state.state?this.props.one:this.props.two}
</div>
)
}
十三、 react-router
2.0 完全向后兼容 1.0,所有在 1.0 被弃用的 API 都会在控制台以 warn 的形式打印出来,在 3.0 中将会完全移除 1.0 所弃用的东西,现在最新版本是4.2.0版本;但是4.0版本后,有很多方法不再向前兼容(这里讲解的为2.8.1版本)
(一) 基本用法:
1、 首先我们需要导入一些组件...
import { Router, Route, Link } from 'react-router'
2、 配置路由信息:
使用jsx语法
React.render((
<Router>
<Route path="/" component={App}>
<Route path="about" component={About}/>
<Route path="inbox" component={Inbox}/>
</Route>
</Router>
), document.body)
使用js对象进行配置
const routes ={
path:'/',
component: App,
childRoutes:[
{ path:'about', component: About },
{ path:'inbox', component: Inbox },
]
}
React.render(<Router routes={routes}/>, document.body)
3、 获取参数
通过动态路由片段
<Route path="messages/:id" component={Message}/>
可以通过如下方式进行获取id:
this.props.params.id
通过query字符串
/foo?bar=baz;
this.props.location.query.bar
(二) 路由的配置规则
1、 路径语法:
:paramName – 匹配一段位于 /、? 或 # 之后的 URL。 命中的部分将被作为一个参数
() – 在它内部的内容被认为是可选的
* – 匹配任意字符(非贪婪的)直到命中下一个字符或者整个 URL 的末尾,并创建一个 splat 参数
如下解释:
// 匹配 /hello/michael 和 /hello/ryan
<Route path="/hello/:name">
// 匹配 /hello, /hello/michael 和 /hello/ryan
<Route path="/hello(/:name)">
// 匹配 /files/hello.jpg 和 /files/path/to/hello.jpg
<Route path="/files/*.*">
(三) 常用路由类型:
browserHistory:react推荐使用(采用的是html5的浏览器路由纪录)
hashHistory:默认的(采用的是hash的浏览器路由纪录)
createMemoryHistory
使用browserHistory如下:
import{ browserHistory } from 'react-router'
<Router history={browserHistory}>
(四) 默认路由的设置:
jsx:
<IndexRoute component={Index}/>
js对象:
const routes = {
path:'/',
component:App,
indexRoute:{
component:Index
},
childRoutes: [
{ path:'list', component:List },
]
}
在嵌套路由中使用{this.props.children}来渲染动态路由,这里相当于vue-router中的router-view
十四、 PropTypes类型检查
在react的15.5版本后,React.PropTypes已经废弃了,需要单独引入prop-types库进行验证
importPropTypes from 'prop-types'
class Children extends Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
<p>这里是子组件</p>
{this.props.name}
</div>
)
}
}
Children.propTypes={
name:PropTypes.string
}
prop-types库可以校验的类型如下:
importPropTypes from 'prop-types';
MyComponent.propTypes = {
// 你可以将属性声明为以下 JS 原生类型
optionalArray:PropTypes.array,
optionalBool:PropTypes.bool,
optionalFunc:PropTypes.func,
optionalNumber:PropTypes.number,
optionalObject:PropTypes.object,
optionalString:PropTypes.string,
optionalSymbol:PropTypes.symbol,
// 任何可被渲染的元素(包括数字、字符串、子元素或数组)。
optionalNode:PropTypes.node,
// 一个 React 元素
optionalElement:PropTypes.element,
// 你也可以声明属性为某个类的实例,这里使用 JS 的
// instanceof 操作符实现。
optionalMessage:PropTypes.instanceOf(Message),
// 你也可以限制你的属性值是某个特定值之一
optionalEnum:PropTypes.oneOf(['News', 'Photos']),
// 限制它为列举类型之一的对象
optionalUnion:PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
// 一个指定元素类型的数组
optionalArrayOf:PropTypes.arrayOf(PropTypes.number),
// 一个指定类型的对象
optionalObjectOf:PropTypes.objectOf(PropTypes.number),
// 一个指定属性及其类型的对象
optionalObjectWithShape:PropTypes.shape({
color:PropTypes.string,
fontSize:PropTypes.number
}),
// 你也可以在任何 PropTypes 属性后面加上 `isRequired`
// 后缀,这样如果这个属性父组件没有提供时,会打印警告信息
requiredFunc:PropTypes.func.isRequired,
// 任意类型的数据
requiredAny:PropTypes.any.isRequired,
// 你也可以指定一个自定义验证器。它应该在验证失败时返回
// 一个 Error 对象而不是 `console.warn` 或抛出异常。
// 不过在 `oneOfType` 中它不起作用。
customProp:function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
returnnewError(
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
},
// 不过你可以提供一个自定义的 `arrayOf` 或 `objectOf`
// 验证器,它应该在验证失败时返回一个 Error 对象。它被用
// 于验证数组或对象的每个值。验证器前两个参数的第一个是数组
// 或对象本身,第二个是它们对应的键。
customArrayProp:PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/matchme/.test(propValue[key])) {
returnnewError(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
};