根据胡子大哈的文章学习,感谢大胡分享
胡子大哈-react.js第一阶段
2017.6.19更新完毕
2017.6.18更新至state
正文开始
毕设搞定,工作搞定,终于。。。其实也不是没有时间,而是心里不静,不想写。
公司要用react,所以,先复习下,开始
一、什么是组件化?
总的来说,各种框架只是为了解决一个问题——开发效率。组件化是一种解决方案。
前端说起来也就三部分——结构,样式,交互。将某一功能的这三部分抽象出来,提升复用性、可维护性、代码效率。
那么问题的关键是——如何抽象?
- 较广泛的属性——>较特殊的属性
- 输入不同——>输出不同
直白点就是类、函数。很明显,对于前端也就是,用js生成想要的结构,并为其添加功能和样式。
具体分析
用js生成页面,再直白点就是——一个字符串形式的dom结构,用js解析、加入功能,然后插入到页面。
1.结构
那么首先要有一个render函数,返回这个字符串形式的dom结构。(也就是最开始的输入)
class oneComponent{
render() {
this.el = this.createDOM(`字符串dom结构`);
return this.el;
}
createDOM(str) {
const div = document.createElement('div');
div.innerHTML = str;
return div;
}
}
结构有了,但显然不够,还要有交互
2.交互
一个思想——状态机。
用一些变量来表示状态,不同的状态,对应不同的页面展示。
首先要有一个状态池,页面的展示由多个状态决定,当状态改变时候,自动重新渲染所有dom。
上边的思路明显有一个问题,自动渲染所有dom开销太大,虽然避免了手工操作dom的各种弊端。解决方案是虚拟dom,其技术细节还有待研究。
首先是状态池:
class oneComponent{
constructor() {
this.state = {};
}
setState (state) {
this.state = state;
this.render();
}
render () {
this.el = this.createDOM(str);
return this.el;
}
// 功能函数
createDOM(str) {
const div = document.createElement('div');
div.innerHTML = str;
return div;
}
}
有了状态池,功能就很好做了。比如一个点击切换内容功能(实际情况的肯定比较复杂了)
class oneComponent{
constructor() {
this.state = {
isShowOK: true;
};
}
setState (state) {
this.state = state;
this.render();
}
render () {
this.el = this.createDOM(`<span>${this.state.isShowOK ? 'ok' : '不ok'}</span>`);
this.el.addEventListener('click', this.clickChangeContent.bind(this), false);
return this.el;
}
// 交互功能
clickChangeContent () {
this.setState ({
isShowOK: !this.state.isShowOK
})
}
// 功能函数
createDOM(str) {
const div = document.createElement('div');
div.innerHTML = str;
return div;
}
}
3.插入页面
这一部分其实是在使用组件,有两个阶段:
- 第一次使用
第一次使用组件显然不是自己的事情,而是父组件的事情。 - 状态改变
每次状态改变,组件自身都要做到自我更新。
class oneComponent{
constructor() {
this.state = {
isShowOK: true;
};
}
setState (state) {
let oldEl = this.el;
this.state = state;
this.render();
if (this.onStateChange) {
// 留下处理改变的接口
this.onStateChange(oldEl, this.el);
}
}
render () {
this.el = this.createDOM(`<span>${this.state.isShowOK ? 'ok' : '不ok'}</span>`);
this.el.addEventListener('click', this.clickChangeContent.bind(this), false);
return this.el;
}
// 交互功能
clickChangeContent () {
this.setState ({
isShowOK: !this.state.isShowOK
})
}
// 以下功能函数
// 组件初始化
init() {
this.render();
}
// 生成dom str->dom
createDOM(str) {
const div = document.createElement('div');
div.innerHTML = str;
return div;
}
// 使用子组件 这个过程发生在解析父组件自身dom的过程中。遇到了组件的语法,就调用这个函数,并传入对应组件的实例。
useChildComponent(childComponent) {
let el = childComponent.init();
功能:将el插入到对应的位置。
}
// 自身更新
onStateChange (oldEl, newEl) {
功能:对比前后的dom,哪变化了,就更新哪。
}
}
稍微抽象下
输入输出上抽象,组件大概就是上边这样,当然有的功能太复杂,就用文字直接简要说明了下。
虽然看起来还不错,但是明显,组件本身也是可以再抽象一下基类。
为了更灵活,可以对基类子类加入props为组件进行配置。这样可定制性更高了。
class Component {
constructor (props = {}) {
this.props = props;
}
setState (state) {
let oldEl = this.el;
this.state = state;
this.renderDOM();
if (this.onStateChange) {
// 留下处理改变的接口
this.onStateChange(oldEl, this.el);
}
}
renderDOM () {
this.el = this.createDOM(this.render());
此处在解析dom过程中添加事件。类似下边注释里边的。
// this.el.addEventListener('click', this.clickChangeContent.bind(this), false);
return this.el;
}
// 组件初始化
init() {
this.renderDOM();
}
// 生成dom str->dom
createDOM(str) {
const div = document.createElement('div');
div.innerHTML = str;
return div;
}
// 使用子组件 这个过程发生在解析父组件自身dom的过程中。遇到了组件的语法,就调用这个函数,并传入对应组件的实例。
useChildComponent(childComponent) {
let el = childComponent.init();
功能:将el插入到对应的位置。
}
// 自身更新
onStateChange (oldEl, newEl) {
功能:对比前后的dom,哪变化了,就更新哪。
}
}
class oneComponent extends Component{
constructor() {
super(props);
this.state = {
isShowOK: true;
};
}
render () {
return `<span>${this.state.isShowOK ? 'ok' : '不ok'}</span>`;
}
// 交互功能
clickChangeContent () {
this.setState ({
isShowOK: !this.state.isShowOK
})
}
}
组件化的概念结束,接下来是基础知识复习
总结一下
组件化设计需要:
- 像上边这样的组件类
- 特殊的语法分析器
- 虚拟dom
二、基础知识
1.杂项
项目生成
使用create-react-app
组件要引入react、
{ Component }
、react-dom
JSX
将js文件中的类似html的语法结构解析成js对象
JSX-->(babel+React.js)-->js对象-->(ReactDOM.render)-->DOM-->插入页面-
为什么react和react-dom要分离开
- 构造出的js对象不一定非要渲染成dom,还有可能渲染到canvas上,或者手机app上。
- 方便更新组件,使用算法操作这个对象,然后整体更新,减少重排,比较快。
2.组件render方法
写react就是写组件,每个组件都必须有一个render方法,返回一个JSX元素。
注意:
- 返回中,最外层只要一个元素
- 在JSX中,class要用className,for要用htmlFor
- JSX中用
{}
插入表达式- 表达式可以是循环,条件,函数,等等
- 表达式中的变量来自组件作用域
3.事件监听
在JSX中的原生HTML标签(注意:对组件没用),为对应的事件接口添加回调即可。
比如onClick,onKeyDown
react事件列表
- event对象
react封装的对象,属性与浏览器自己的event对象基本一致,保证了浏览器兼容性,对外api符合w3c标准。
- 回调中的this
单纯指定回调,不绑定this的话,是无法在回调函数中通过this拿到组件实例的。
为什么?
很简单,回调函数直接将引用传递出去,其真正执行阶段的作用域是在全局作用域下,也就是说,这是一个在全局环境执行,但是没有明确指出是window对象调用的函数。
es5规定,在严格模式下,不直接用window对象调用函数,其内部this为undefined。react当然用的严格模式。
所以,拿不到组件实例,也拿不到window,最后是一个undined。
解决方法:
method.bind(this, arg1, arg2, ...)
4.state
状态池,就像上一部分说的。
注意:
- 在组件的构造器中定义
- state状态自动更新要使用
setState()
方法,以便于自动触发render -
setState()
参数 - 对象——表示组件需要更新的状态。
- 函数
-
setState()
的使用
- 更新对象放入更新队列中,在本趟结束后更新。所以,前后数据改变存在逻辑关系的话,在同一个函数中,不能只传入对象,解决方法如下。
- 使用函数参数解决上边的问题,参数属性为上一个setState的结果。这种情况下,react会把setState一趟更新队列中的状态合并,并一次渲染。
- 传入第一个对象参数,并传入第二个当状态改变后的回调函数,封装成一个promise,当成异步来处理。这种情况的话,显然是更新了三次。
5.props
配置组件,组件是比较抽象的,实例化组件就是一个特殊化的过程,通过props来配置。
父组件调用子组件时候,通过标签特性传入。所有的标签特性都会对应到子组件的props字段上。
可以传递任何类型的值。比如函数,对象。
props一旦传入进来,就不能被改变。这是为了使得组件的形态/行为可以预测。
只有通过父组件重新渲染,才能改变props。
默认props(defaultProps)
static defaultProps = {
likedText: '取消',
unlikedText: '点赞'
}
6.state与props
总结一下:
state组件自己控制自己的状态池,组件外部不可改变。
props父组件对子组件的初始化状态池,子组件内部不可改变。
没有state的组件,叫做无状态组件,更利于组建维护。因为有了内部状态,就意味着组件很复杂,复用性降低。
react.js鼓励无状态组件。甚至在后来版本引入了函数式组件——不能用state的组件。
const dog = (props) => {
const say = (event) => alert('dog')
return (
<div onClick={say}>dog</div>
)
}
7.渲染列表
渲染列表的一般步骤:
拿到数组-->map遍历-->返回元素-->罗列渲染
- 注意key值
key,元素的标识,一般是后台数据的id。
(完)