您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
react+redux+koa技术栈实现小结
 
作者:ouvens 发布于 2017-3-21
  1848  次浏览      14
 

虽然别人整理的入门知识资料已经挺多的了,但不一定适合自己,还是重新整理下,理一理React的开发生态。

一、React 安装使用

使用react,只需要引入react.min.js(React 的核心库)和react-dom.min.js(提供与 DOM 相关的功能)即可。

<script src="../js/lib/react.min.js"></script>
<script src="../js/lib/react-dom.min.js"></script>

<div id="example"></div>
<script type="text/babel">
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('example')
);
// 这段代码将一个h1标题,插入id="example"节点中
</script>

通常为了使用JSX语法,我们需要在文件打包时使用插件将含有JSX语法的文件解析成普通的JavaScript语法,例如fis3-parser-react;如果使用到了ES6的写法,也需要将其转为ES5的格式,例如使用fis3-parser-babel。(现在前端已经不用ES6了,babel太慢影响效率,ES6在Node上使用)

二、React JSX

React使用JSX来替代常规的 JavaScript,以为JSX执行时进行了优化,含有错误检测而且方便我们书写模板。

var arr = [
<h1>W3Cschool教程</h1>,
<h2>从W3Cschool开始!</h2>,
];
var mystyle = {
fontSize: 100,
color: '#FF0000'
};
ReactDOM.render(
<div style={myStyle}>{arr}</div>,
document.getElementById('example')
);

JSX语法代码实际上是JavaScript代码,所以html中的部分标签属性在JSX中不能直接使用,例如html class属性在JSX中需要使用className表示,for属性需要使用htmlFor代替,style属性标签中内容需要写成JSON格式。同时,JSX使用单个{}来包含变量模板,一次返回的JSX render内容必须放在单独一个标签内,返回统计的多个标签是不允许的,例如下面这样是不对的:

// 不正确
render: function(){
return (<div>{this.props.name}</div>
<div>{this.props.site}</div>
);
}

三、React组件

实现了输出网站名字和网址的组件,另外if和for循环的输出大家也可以注意下:

var Name = require('Name');
var Link = require('LINK');
var list = ['list-item-1', 'list-item-2', 'list-item-3'];

var WebSite = React.createClass({
getDefaultProps: function() {
return {
name: '极限前端',
site: "http://www.jixianqianduan.com"
};
},
render: function() {
var hasList = list.length > 0;
if(hasList){
return (
<div>
<Name name={this.props.name} />
<Link site={this.props.site} />
<ul>
{
this.props.list.map(function(item, index){
return <li key={index}>item</li>;
});
}
</ul>
</div>
)
}else{
return <div>没有数据</div>;
}
}
});

React.render(
<WebSite name="极限前端" site="http://www.jixianqianduan.com" />,
document.getElementById('example')
);

// 文件Name.js
var Name = React.createClass({
render: function() {
return (
<h1>{this.props.name}</h1>
);
}
});

module.exports = Name;

// 文件Link.js
var Link = React.createClass({
render: function() {
return (
<a href={this.props.site}>
{this.props.site}
</a>
);
}
});

module.exports = Link;

最新版的React组件名称只允许使用大些字母开头的变量命名,例如x-button、my-button等命名方式都是不正确的。另外注意上面if模板和for循环输出模板的用法,循环输出时React要求列表项便签需要加上key的属性。

四、React状态state和属性props

下面展示的一个典型实例中创建了 LikeButton 组件,getInitialState 方法用于定义初始状态,也就是一个对象,这个对象可以通过 this.state 属性读取。当用户点击组件,导致状态变化,this.setState 方法就修改状态值,每次修改以后,自动调用 this.render 方法,再次渲染组件。

var LikeButton = React.createClass({
getInitialState: function() {
return {liked: 0};
},
handleClick: function(event) {
this.setState({liked: this.state.liked + 1});
},
render: function() {
var text = this.state.liked ? this.state.liked : '不喜欢';
return (
<p onClick={this.handleClick}>
你<b>{text}</b>我。点我切换状态。
</p>
);
}
});

React.render(
<LikeButton />,
document.getElementById('example')
);

上面的例子也可以这样来做:

var Name = require('Name');
var Link = require('LINK');
var list = ['list-item-1', 'list-item-2', 'list-item-3'];

var WebSite = React.createClass({
getDefaultProps: function() {
return {
name: '极限前端',
site: "http://www.jixianqianduan.com"
};
},

// 属性类型检测
propTypes: {
name: React.PropTypes.string.isRequired,
site: React.PropTypes.string.isRequired
},

getInitialState: function() {
return {
name: '极限前端',
site: "http://www.jixianqianduan.com"
};
},

render: function() {
var hasList = list.length > 0;
if(hasList){
return (
<div>
<Name name={this.state.name} />
<Link site={this.state.site} />
<ul>
{
this.props.list.map(function(item, index){
return <li key={index}>item</li>;
});
}
</ul>
</div>
)
}else{
return <div>没有数据</div>;
}
}
});

React.render(
<WebSite name="极限前端" site="http://www.jixianqianduan.com" />,
document.getElementById('example')
);

state 和 props 主要的区别在于 props 是不可变的,而 state 可以根据与用户交互来改变。这就是为什么有些容器组件需要定义 state 来更新和修改数据。 而子组件只能通过 state 来传递数据。

组件的state和props常见有下面的管理方法:

   设置状态:setState, 不能在组件内部通过this.state修改状态,因为该状态会在调用setState()后被替换,
setState()并不会立即改变this.state,而是创建一个即将处理的state。setState()并不一定是同步的,
为了提升性能React会批量执行state和DOM渲染。setState()总是会触发一次组件重绘,

除非在shouldComponentUpdate()中实现了一些条件渲染逻辑。

替换状态:replaceState,replaceState()方法与setState()类似,但是方法只会保留nextState中状态,

原state不在nextState中的状态都会被删除。

设置属性setProps,props相当于组件的数据流,它总是会从父组件向下传递至所有的子组件中。当和一个外部的JavaScript应用集成时,

我们可能会需要向组件传递数据或通知React.render()组件需要重新渲染,可以使用setProps()。

替换属性replaceProps,replaceProps()方法与setProps类似,但它会删除原有

强制更新:forceUpdate,forceUpdate()方法会使组件调用自身的render()方法重新渲染组件,组件的子组件也会调用自己的render()。但是

,组件重新渲染时,依然会读取this.props和this.state,如果状态没有改变,那么React只会更新DOM。

获取DOM节点:findDOMNode,如果组件已经挂载到DOM中,该方法返回对应的本地浏览器 DOM 元素。

判断组件挂载状态:isMounted,isMounted()方法用于判断组件是否已挂载到DOM中。

可以使用该方法保证了setState()和forceUpdate()在异步场景下的调用不会出错。

五、组件生命周期

初始化阶段:

getDefaultProps:获取实例的默认属性(即使没有生成实例,组件的第一个实例被初始化CreateClass的时候调用,只调用一次,) getInitialState:获取每个实例的初始化状态(每个实例自己维护) componentWillMount:组件即将被装载、渲染到页面上(render之前最好一次修改状态的机会)

render:组件在这里生成虚拟的DOM节点(只能访问this.props和this.state;只有一个顶层组件,也就是说render返回值值职能是一个组件;不允许修改状态和DOM输出)componentDidMount:组件真正在被装载之后,可以修改DOM

运行中状态:

componentWillReceiveProps:组件将要接收到属性的时候调用(赶在父组件修改真正发生之前,可以修改属性和状态) shouldComponentUpdate:组件接受到新属性或者新状态的时候(可以返回false,接收数据后不更新,阻止render调用,后面的函数不会被继续执行了)

componentWillUpdate:不能修改属性和状态 render:只能访问this.props和this.state;只有一个顶层组件,也就是说render返回值只能是一个组件;不允许修改状态和DOM输出 componentDidUpdate:可以修改DOM

销毁阶段:

componentWillUnmount:开发者需要来销毁(组件真正删除之前调用,比如计时器和事件监听器)

React的网络请求通常是将请求的url放在组件props中传入,然后在componentDidMount时发送ajax请求。

var UserGist = React.createClass({
getInitialState: function() {
return {
username: '',
lastGistUrl: ''
};
},

componentDidMount: function() {
this.serverRequest = $.get(this.props.source, function (result) {
var lastGist = result[0];
this.setState({
username: lastGist.owner.login,
lastGistUrl: lastGist.html_url
});
}.bind(this));
},

componentWillUnmount: function() {
this.serverRequest.abort();
},

render: function() {
return (
<div>
{this.state.username} 用户最新的 Gist 共享地址:
<a href={this.state.lastGistUrl}>{this.state.lastGistUrl}</a>
</div>
);
}
});

ReactDOM.render(
<UserGist source="https://api.github.com/users/ouven/gists" />,
mountNode
);

六、refs

如果绑定一个 ref 属性到render内容的返回值上,组件或其它组件就可以这样来引用这个内容关联的DOM元素。

<input ref="myInput" />

var input = this.refs.myInput;
var inputValue = input.value;
var inputRect = input.getBoundingClientRect();

我们也可以使用getDOMNode()方法获取当前真实DOM元素,但是findDOMNode()不能用在无状态组件上。此外也可以使用bind的方法来实现当前事件的处理。

componentDidMound() {
const el = findDOMNode(this);
}
// do something ...
onClick = {this.handleClick.bind(this, value1, value2)}

// do something
handleClick(value1, value2, ..., event) {
// 事件处理函数
}

七、服务端渲染(SSR)

React服务端渲染需要用到react-dom/server模块,以koa(koa使用教程省略)为例,我们渲染一个服务端返回的页面就可以这样写:

/**
* react前后端同构页面
* @param {[type]} req [description]
* @param {[type]} res [description]
* @yield {[type]} [description]
*/
const reactController = function*(req, res) {

let ctx = this;
let helloProps = {
type: 'hello',
data: {
name: 'hello-name-init',
address: 'hello-address-init',
age: '26',
job: 'hello-job-init'
}
};

let contentProps = {
type: 'content',
data: {
name: 'content-name-init',
address: 'content-address-init',
age: '26',
job: 'content-job-init'
}
}

let reactHello = reactComponent.renderPath(ctx, 'react/react-hello/main.jsx', helloProps);
let reactContent = reactComponent.renderPath(ctx, 'react/react-content/main.jsx', contentProps);

ctx.body = yield render(ctx, 'pages/react', {
reactHello,
reactContent,
storeData: {
helloProps,
contentProps
}
});
};

这里用到了一个统一react的render处理模块:

'use strict';
const React = require('react');
const ReactDOM = require('react-dom');
const ReactDOMServer = require('react-dom/server');

/**
* 根据开发或正式环境读取不同目录先的jsx组件
* @param {[type]} ctx [当前运行环境,用于判断使用开发环境路径还是线上环境路径]
* @param {[type]} componentPath [接受组件路径名]
* @param {[type]} props [组件接受的数据]
* @return {[type]} [description]
*/
const renderPath = function(ctx, componentPath, props) {
let componentFactory,
jsxPath;
// 如果是本地则使用dev环境目录,否则使用page的构建目录
if (ctx.hostname === '127.0.0.1' || ctx.hostname === 'localhost') {
jsxPath = '../dev/component/';
} else {
jsxPath = '../pages/component/';
}
componentFactory = React.createFactory(require(jsxPath + componentPath));

/**
* renderToStaticMarkup不会避免前端重渲染
* renderToString会避免前端重渲染
*/
return ReactDOMServer.renderToString(componentFactory(props));
};

module.exports = {
renderPath: renderPath
}

这里这样做还不够,我们还需要将renderToString的字符串内容填充到真正的页面模板中,这里以swig模板为例,同时后端渲染的初始数据状态也要通过storeData变量带到前端页面上,保证初始的页面组件状态和后端直出是一样的。

<div id="testHello">
<div></div>
</div>
<div id="test">
<div></div>
</div>
<script>
var storeData = ;
</script>

八、Redux与组件通信

React组件间的通信分为几种,父组件向子组件通信、子组件向父组件通信、同级组件间通信。第一种通常是将父组件的state传给子组件props来实现,父组件state变化,子组件的状态就直接变化;子组件向父组件通信是将父组件的方法通过props传入的子组件中,然后再子组件中调用来通知父组件;同级组件通信则是创建一个共同父组件,先发起子组件向父组件通信,然后再让父组件向另一子组件发起通信。 另一种通用的机制就是Redux,Redux可以创建一个全局的store,统一保存管理不同组件的状态,然后通过subscribe订阅事件,当dispatch调用时可以修改组件状态触发订阅事件重新渲染视图。

var reactContent = require('react-content');
var reactHello = require('react-hello');
var store = Redux.createStore(reducer);

function reducer(state={}, action) {
if(action.type){
state[action.type] = action.data;
}
return state;
};

// do somthing...
for(var key in storeData){
store.dispatch(storeData[key]);
}
// do somthing...
reactContent.init(store);
reactHello.init(store);

此时如果react-content组件重要控制react-hello组件状态的变化,reactHello中就可以这样监听

componentDidMount: function() {

var store = this.props.store;
function handleChange() {
self.setState({ data: store.getState()['hello'] });
}

// 订阅store变化,如果有dispatch,handleChange里面的状态设置就会执行
let unsubscribe = store.subscribe(handleChange);
// unsubscribe(); 取消订阅
}

reactContent中控制reactHello变化的动作中则需要这样写。

// 触发reactHello组件变化
_triggerHello: function(){

// 修改hello组件的store
var hello = this.props.store.getState()['hello'];
hello.name = 'ouvenzhang';

// dispatch后会触发reactHello中的handleChange
this.props.store.dispatch({
type: 'hello',
data: hello
});
}

其实我们可以在SSR时也使用Redux管理,可以增强组件的一部分复用性,但目前还没有用到,其实redux是用来管理组件状态变化的,更推荐在前端使用,服务端使用感觉有点不大必要,服务端只用来做首次内容的渲染。

九、事件

React自定义了一些事件,可以方便我们的开发使用。

stopPropagation() 和 preventDefault(),React标准化了事件对象,因此在不同的浏览器中都会有相同的属性。事件处理器也可以在事件冒泡阶段触发。要在捕获阶段触发某个事件处理器,在事件名字后面追加Capture字符串;例如,使用onClickCapture而不是onClick来在捕获阶段处理点击事件

系统事件

onCopy onCut onPaste

键盘事件

onKeyDown onKeyPress onKeyUp

表单事件

onFocus onBlur onChange onInput onSubmit

鼠标事件

onClick onDoubleClick onDrag onDragEnd onDragEnter onDragExitonDragLeave onDragOver onDragStart onDrop onMouseDown onMouseEnteronMouseLeave onMouseMove onMouseOut onMouseOver onMouseUp

触摸事件

onTouchCancel onTouchEnd onTouchMove onTouchStart

窗口和滚轮事件

onScroll onWheel

十、小结

总得来说react生态的还是很健全的,解决的了实际开发中组件和组件状态管理的问题,前后端同构的模式也解决了React库本身较大前端加载缓慢的弊端,但是如果实际项目中没有Node服务层,个人建议还是不要直接使用React,React库文件比较大,还需要其它的依赖,会大大延后页面渲染时机,这是例如使用Vue会显得更轻量级。

   
1848 次浏览       14
相关文章 相关文档 相关课程



深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程
最新课程计划
信息架构建模(基于UML+EA)3-21[北京]
软件架构设计师 3-21[北京]
图数据库与知识图谱 3-25[北京]
业务架构设计 4-11[北京]
SysML和EA系统设计与建模 4-22[北京]
DoDAF规范、模型与实例 5-23[北京]

Android手机开发(一)
理解Javascript
非典型ajax实践
彻底的Ajax
javascript 使用Cookies
使用 jQuery 简化 Ajax 开发
更多...   

Struts+Spring+Hibernate
基于J2EE的Web 2.0应用开发
J2EE设计模式和性能调优
Java EE 5企业级架构设计
Java单元测试方法与技术
Java编程方法与技术

某航空公司IT部 JavaScript实践
某电视软件 HTML5和JavaScript
中航信 JavaScript高级应用开发
大庆油田 web界面Ajax开发技术
和利时 使用AJAX进行WEB应用开发
更多...