[Full-stack] 前端框架鼻祖 - React.js

react

故事背景


[1] 博客笔记结合《React快速上手开发》再次系统地、全面地走一遍。

[2] React JS Tutorials:包含了JS --> React --> Redux --> Mobx 

[3] 重新学习,不得不学:【尚硅谷】2021 React全家桶【React17】

 顺便把过去的这些辣鸡都整理一遍。

项目部署


着眼于ful-stack全局,了解前后端的部署,之后才能深刻理解react的角色与位置。

1. 服务器部署

EC2

2. 用户权限管理

[AWS] OAuth2.0

[AWS] User management

Cognito

3. 这就是未来后端的趋势

[AWS] Serverless

React基本路线


一、静态的html变为了动态生成 

  • React自己的html表达方式

    <script>

ReactDOM.render(
React.DOM.h1(

{id: "my-heading"},

React.DOM.span(null,

React.DOM.em(null, "Hell"),

"o"

),

" world!"

),

document.getElementById('app')

);

</script>

  • 命名冲突:class, for, style

    <script>

ReactDOM.render(

/*

// COUNTEREXAMPLE

// this doesn't work

React.DOM.h1(

{

class: "pretty",

for : "me",

},

"Hello world!"

),

*/

// PROPER EXAMPLE

// this works

React.DOM.h1(

{

className: "pretty",

htmlFor : "me",

},

"Hello world!"

),

document.getElementById('app')

);

</script>

style倒是经常用到。

    <script>

ReactDOM.render(

/*

// COUNTEREXAMPLE

// this doesn't work

React.DOM.h1(

{

style: "background: black; color: white; font-family: Verdana",

},

"Hello world!"

),

*/

// PROPER EXAMPLE

// this works

React.DOM.h1(

{

style: {

background: "black",

color: "white",

fontFamily: "Verdana",

}

},

"Hello world!"

),

document.getElementById('app')

);

</script>

思考:可见以上的写法,可读性比较差,这也是之后引入JSX的重要原因。

下图中上面的JSX写法就接近了原始的HTML格式,看起来更更为习惯。

Goto for more details: [React] 01 - Intro: javaScript library for building user interfaces - React JSX

二、自定义组件才是React的威力

  • 对React.DOM做了封装

    <script>
var Component = React.createClass({

render: function() {

return React.DOM.span(null, "I'm so custom");

}

});

ReactDOM.render(

React.createElement(Component),  // 这里之前是React.DOM代码,现在封装了起来

document.getElementById("app")

);
</script>

附加题:React 工厂方法——createFactory使用详解【为何感觉用处不是很大】

 

  • props - 获取属性

    <script>
var Component = React.createClass({
/**
* 可以采用PropTypes检查props
*/

/**
* 如果没有name属性,会报错,
* 可以设置一个默认的
*/

render: function() {

return React.DOM.span(null, "My name is " + this.props.name);

}
});
ReactDOM.render(

React.createElement(Component, {

name: "Bob",

}),

<Component name="Bob" /> // <---- 看来JSX的写法更为舒服,createElement看着别扭!

document.getElementById("app")

);
</script>

附加题:react demo10 (设置组件属性的默认值getDefaultProps)

复合组件:Composite Component 表示 创建多个组件来合成一个组件,把组件的不同功能点进行分离。Goto for more details: 复合组件,React Props

子节点的获取:[React] 01 - Intro: javaScript library for building user interfaces - this.props.children

 

  • state - 通过状态改变设置属性

    <script>

var TextAreaCounter = React.createClass({

propTypes: {

text: React.PropTypes.string,

},

getDefaultProps: function() {

return {

text: '',

};

},

getInitialState: function() {

return {

text: this.props.text,

};

},

_textChange: function(ev) {

this.setState({            // Jeff: setState 触发界面更新,因为这里把prop变了

text: ev.target.value,      // event.target:使用合成事件来消除浏览器之间的不一致情况

});

},

render: function() {

return React.DOM.div(null,

React.DOM.textarea({

value : this.state.text,

onChange: this._textChange,

}),

React.DOM.h3(null, this.state.text.length)

);

}

});
ReactDOM.render(

React.createElement(TextAreaCounter, {

text: "Bob", // 这里其实是个初始值,所以命名为defaultValue会好些

}),

document.getElementById("app")

);

</script>

附加题:事件处理,书p20。 

React 组件 API,除了setState,还有其他6种方法:

    • 设置状态:setState
    • 替换状态:replaceState
    • 设置属性:setProps
    • 替换属性:replaceProps
    • 强制更新:forceUpdate
    • 获取DOM节点:findDOMNode
    • 判断组件挂载状态:isMounted

 

  • 从外部访问组件

如何定义”外部“?通过 myTextAreaCounter 设置新的state值 as following。

myTextAreaCounter.setState({text: "Hello outside world!" }); 

以下定义了myTextAreaCounter。

    <script>


var TextAreaCounter = React.createClass({

propTypes: {

defaultValue: React.PropTypes.string,

},

getInitialState: function() {

return {

text: this.props.defaultValue,

};

},
_textChange: function(ev) {

this.setState({

text: ev.target.value,

});

},
render: function() {

return React.DOM.div(null,

React.DOM.textarea({        // text --> input

value : this.state.text,

onChange: this._textChange,

}),

React.DOM.h3(null, this.state.text.length)

);

}

});

---------------------------------------------------------
var myTextAreaCounter = ReactDOM.render(

React.createElement(TextAreaCounter, {

defaultValue: "Bob",

}),

document.getElementById("app")

);
</script>

附加题:组件也算是代码"分离思想"的一种体现,参看笔记:【from HTML静态页面 to 复合组件】

 

  • 组件间的传值

参见笔记 [React] 09 - Tutorial: components

 (11) 子组件向父组件传值 - 子组件调用父组件的“方法”,以”函数指针“的类似形式;

 (12) 子组件之间的传值 

 (13) 双向数据绑定

[Redux]

通过这件事,让我们明白了Redux的重要性。

Goto for more details: [React] 02 - Intro: why react and its design pattern - 背后的思想:一步一步进化到 Redux

以及相关笔记: 

  • [React] 11 - Redux: redux
  • [React] 12 - Redux: async & middleware
  • [React] 13 - Redux: react-redux
  • [React] 14 - Redux: Redux Saga
  • [React] 15 - Redux: TodoMVC
  • [看漫画,学 Redux] —— A cartoon intro to Redux
  • 如何评价数据流管理架构 Redux? - 杨森

有必要另起一文走一遍!

 

 

三、组件的生命周期

  • 组件的创建

Ref: React创建组件的三种方式及其区别

    1. 函数式定义的无状态组件

    2. es5原生方式React.createClass定义的组件  【`React.createClass`是react刚开始推荐的创建组件的方式,这是ES5的原生的JavaScript来实现的React组件】

    3. es6形式的extends React.Component定义的组件

随着React的发展,React.createClass形式自身的问题暴露出来:

    • React.createClass 会自绑定函数方法(不像React.Component只绑定需要关心的函数)导致不必要的性能开销,增加代码过时的可能性。
    • React.createClass 的mixins不够自然、直观;
    • React.Component 形式非常适合高阶组件(Higher Order Components -- HOC),它以更直观的形式展示了比mixins更强大的功能,并且HOC是纯净的JavaScript,不用担心他们会被废弃。
    • HOC 可以参考 无状态组件(Stateless Component) 与高阶组件。
    • mixins将死,ES6的Class不对其进行支持,HOC就是解决办法。

 

(1) 当前老旧写法:

var InputControlES5 = React.createClass({
propTypes: {//定义传入props中的属性各种类型

initialValue: React.PropTypes.string

},

defaultProps: { //组件默认的props对象

initialValue: ''

},

// 设置 initial state

getInitialState: function() {//组件相关的状态对象

return {

text: this.props.initialValue || 'placeholder'

};

},

handleChange: function(event) {

this.setState({ //this represents react component instance

text: event.target.value

});

},

render: function() {

return (

<div>

Type something:

<input onChange={this.handleChange} value={this.state.text} />

</div>

);

}

});

--------以下没区别------------------------

InputControlES5.propTypes = {

initialValue: React.PropTypes.string

};

InputControlES5.defaultProps = {

initialValue: ''

};

(2) 未来推崇写法:【其实就是使用了js6的类的新特性】

class InputControlES6 extends React.Component {
constructor(props) {

super(props);

// 设置 初始状态

this.state = {

text: props.initialValue || 'placeholder'

};

// ES6 类中函数必须手动绑定

this.handleChange = this.handleChange.bind(this);  // React.Component创建的组件,其成员函数不会自动绑定this,需要开发者手动绑定,否则this不能获取当前组件实例对象。 

}

handleChange(event) {

this.setState({

text: event.target.value

});

}

render() {

return (

<div>

Type something:

<input onChange={this.handleChange}

value={this.state.text} />

</div>

);

}

}

--------以下没区别------------------------

InputControlES6.propTypes = {

initialValue: React.PropTypes.string

};

InputControlES6.defaultProps = {

initialValue: ''

};

Jeff:
但是,使用类的概念后,意思就自然而然地跟着变了:
作为组件类的属性,不是组件实例的属性,也就是所谓的类的静态属性来配置的。

通过bind锁定this:

  1. 可以在构造函数中完成绑定,【以上例子】

  2. 也可以在调用时使用method.bind(this)来完成绑定,
  3. 还可以使用arrow function来绑定。

<div onClick={this.handleClick.bind(this)}></div>    // 2.使用bind来绑定
<div onClick={()=>this.handleClick()}></div> // 3.使用arrow function来绑定

 

  • 无状态组件

1、只要有可能,尽量使用无状态组件创建形式。

2、否则(如需要state、生命周期方法等),使用`React.Component`这种es6形式创建组件。

【原文看似不错】

无状态的函数写法,又称为纯组件SFC。它是一种只负责展示的纯组件。 

对于这种无状态的组件,使用函数式的方式声明,会使得代码的可读性更好,并能大大减少代码量,箭头函数则是函数式写法的最佳搭档:

const Todo = (props) => (

<li

onClick={props.onClick}

style={{textDecoration: props.complete ? "line-through" : "none"}}

>

{props.text}

</li>

)

上面定义的 Todo 组件,输入输出数据完全由props决定,而且不会产生任何副作用。

对于props为 Object 类型时,我们还可以使用 ES6 的解构赋值:

const Todo = ({ onClick, complete, text, ...props }) => (

<li

onClick={onClick}

style={{textDecoration: complete ? "line-through" : "none"}}

{...props}

>

{props.text}

</li>

)

无状态组件一般会搭配高阶组件(简称:HOC)一起使用,高阶组件用来托管state,Redux 框架就是通过 store 管理数据源和所有状态,其中所有负责展示的组件都使用无状态函数式的写法。

这种模式被鼓励在大型项目中尽可能以简单的写法 来分割原本庞大的组件,而未来 React 也会面向这种无状态的组件进行一些专门的优化,比如避免无意义的检查或内存分配。所以建议大家尽可能在项目中使用无状态组件。

无状态组件内部其实是可以使用ref功能的,虽然不能通过this.refs访问到,但是可以通过将ref内容保存到无状态组件内部的一个本地变量中获取到。

Ref: React无状态组件——为可复用而生

如何利用react提供的jsx语法写好一个可复用的组件呢?---- 最常用的组件是“无状态组件”

所谓无状态,也可以叫做无生命周期,无state,组件是一个纯jsx类或者对象。

这个组件内部没有任何的生命周期和state状态,那么如果需要管理state状态,该怎么办呢?---- Redux

# 这里自定义了一个head主键,可以复用,因为head的各部分细节由参数控制。

export class Header extends Component {

render() {

const {title, imgUrl, linkTo, bgColor} = this.props
//提供4个接口参数给父容器做设置,可以不传参。

return (

<header className='header' style={bgColor}>

{title}

<Link to={linkTo} className="a_link" >

<img src={imgUrl} className="a_img" />

</Link>

</header>

)

}
//严格来说,这些暴露给外部的参数都需要做验证,常用的验证类型为array,bool,func,number,object,string

static propTypes = {

title: React.PropTypes.string.isRequired

}

 

 

For more details, go to: [React] 16 - Topic: Mixins & Higher-Order Components

React.Component这种形式并不支持Mixins,至今React团队还没有给出一个该形式下的官方解决方案;

但是React开发者社区提供一个全新的方式来取代Mixins,那就是 Higher-Order Components。

所谓 HOC:会返回组件的组件,Redux就是一个实现例子,可处理状态。

由于React团队已经声明 React.createClass最终会被React.Component的类形式所取代。

但是在找到Mixins替代方案之前是不会废弃掉React.createClass形式。所以:

"能用React.Component创建的组件的就尽量不用React.createClass形式创建组件"

 

 

  • 三个状态,七个方法

参见笔记 诱人的 react 视频教程-基础篇

【代码有必要详细研究与实践】 

实验代码的基本思路是:

"点击按钮 ----> 改变某state ----> 触发render ----> 通过if判断语句,跳过某component的渲染 ----> 达到组件消失的效果“

 

 

 

路由:react-router


核心待解决的问题:没有登录的话,就没有权限访问这个路由。 

RR4 本次采用单代码仓库模型架构(monorepo),这意味者这个仓库里面有若干相互独立的包,分别是:

  • react-router           # React Router 核心

  • react-router-dom              # 用于 DOM 绑定的 React Router

  • react-router-native       # 用于 React Native 的 React Router

  • react-router-redux         # React Router 和 Redux 的集成

  • react-router-config       # 静态路由配置的小助手

一、传统方式

[React] 05 - Route: connect with ExpressJS

[React] 06 - Route: koa makes your life easier

可见,路由的处理采用的是:在后端代码中采用类似switch的方式判断前端发来的URL request。

那么问题来了,react是前端的东西,react-router是前端还是后端?或者,是一个介于中间的东西?

答案是:前端路由!

二、React-router方式

参见:[React] 10 - Tutorial: router

React Router 使用教程,在此补充些遗漏的内容。

Ref: 官方路由示范代码

 

  • 嵌套路由 + hash

如此,不用使用关键字:exact

用户访问/repos时,

    1. 先加载App组件,
    2. 然后在它的内部再加载Repos组件。

<Router history={hashHistory}>

<Route path="/" component={App}>

<Route path="/repos" component={Repos}/>

<Route path="/about" component={About}/>

</Route>

</Router>

URL with hash value.

http://localhost:8080/#/about?_k=z86gvy

  

  • props.children

export default React.createClass({

render() {

return (

<div>

<h1>React Router Tutorial</h1>

<ul role="nav">

<li><Link to="/about">About</Link></li>

<li><Link to="/repos">Repos</Link></li>

</ul>

{this.props.children}

</div>

)

}

})

Result:  

  • ... this.props

导入所有的参数并展开

<ul role="nav">

<li><NavLink to="/about">About</NavLink></li>

<li><NavLink to="/repos">Repos</NavLink></li>

</ul>

将Link 统一变为记录活动状态带有颜色的link。

  render() {

return <Link {...this.props} activeClassName="active"/>

}

 

更多内容:

详见 URL的参数处理 

 

  • 其他内容

十一、表单处理

十二、路由的钩子

Jeff: 以上便是react周边最为亲近的一些知识点


资源一:

In React JS Tutorials, lectures from 9. 

From: React高级篇(一)从Flux到Redux,react-redux

从Flux到Redux,再到react-redux,从这个简短历程中,我们可以看到框架设计上的演进,而redux + react-redux也是React开发万家桶的标配。

到了这里,可以忘记Flux啦~

资源二:

[React] 07 - Flux: react communicates with mongodb 

[React] 11 - Redux: redux

[React] 12 - Redux: async & middleware

[React] 13 - Redux: react-redux

[React] 14 - Redux: Redux Saga

[React] 15 - Redux: TodoMVC       

资源三:

文档:https://redux.js.org/basics/example-todo-list

文档:Redux 官方TODO示例解析

代码:redux/examples/todomvc/

Redux-saga 中文文档,乃高手必备之物。

江湖有云:先实践一下,然后再看以上文档和示例代码。

单向流 Flux


一、实现一个简单的例子

详述:[React] 07 - Flux: react communicates with mongodb 

实现的思路要点:

1. 在store中有一个数组(state)来表现new item。

2. 点击按钮,发送信号(action),改变状态,view监听到状态后刷新(重新表现items)

 

二、有什么弊端

dispatch与store的问题,过于灵活。

参考:[React] 02 - Intro: why react and its design pattern - 背后的思想:一步一步进化到 Redux

学习: 《看漫画,学 Redux》 —— A cartoon intro to Redux

 

问题1:store 的代码无法被热替换,除非清空当前的状态

改变下store的代码,状态没了!不利于调试(热替换)。

分开保存即可。

 

问题2:每次触发 action 时状态对象都被直接改写了

旧状态管理不稳定,不好实现 “跳回到这个对象之前的某个状态(想象一个撤销功能)”。 

当一个 action 需要 store 响应时,将状态拷贝一份并在这份拷贝的状态上做出修改。

 

问题3:没有合适的位置引入第三方插件

一个简单的例子就是日志。比如说你希望 console.log() 每一个触发的 action 同时 console.log() 这个 action 被响应完成后的状态。

在 Flux 中,你只能订阅(subscribe) dispatcher 的更新和每一个 store 的变动。

但是这样就侵入了业务代码,这样的日志功能不是一个第三方插件能够轻易实现的。

(1) 将这个架构的部分功能包装进其他的对象(中间件)中将使得我们的需求变得更容易实现;

      Goto: [React] 12 - Redux: async & middleware

(2) 使用一个树形结构来组织所有改变状态的逻辑。

进化论 Redux


一、要点概述

学习: 《看漫画,学 Redux》 —— A cartoon intro to Redux

1. Action creators

保留了 Flux 中 action creator 的概念。

Redux 中的 action creator 不会直接把 action 发送给 dispatcher,而是返回一个格式化好的 JavaScript 对象。

 

2. The store

Redux 中的 store 首先会保存整个应用的所有状态,然后将判断 "哪一部分状态需要改变" 的任务分配下去。

而以 root reducer 为首的 reducer 们将会承担这个任务。 

store 已经完全接管了 dispatch 相关的工作。

 

3. The reducers

当 store 需要知道一个 action 触发后状态需要怎么改变时,他会去询问 reducer。

Root reducer 会根据状态对象的键(key)将整个状态树进行拆分,然后将拆分后的每一块子状态传到知道该怎么响应的子 reducer 那里进行处理。

【状态树,子状态,子 reducer】

 

4. 拷贝state后修改

这是 Redux 的核心思想之一。不直接修改整个应用的状态树,而是将状态树的每一部分进行拷贝并修改拷贝后的部分,然后将这些部分重新组合成一颗新的状态树。

子 reducers 会把他们创建的副本传回给根 reducer,而根 reducer 会把这些副本组合起来形成一颗新的状态树。最后根 reducer 将新的状态树传回给 store,store 再将新的状态树设为最终的状态。

如果你有一个小型应用,你可能 只有一个 reducer 对整个状态树进行拷贝并作出修改。

又或者你有一个超大的应用,你可能会有 若干个 reducers 对整个状态树进行修改。这也是 Flux 和 Redux 的另一处区别。

在 Flux 中,store 并不需要与其他 store 产生关联,而且 store 的结构是扁平的。

而在 Redux 中,reducers 是有层级结构的。这种层级结构可以有若干层,就像组件的层级结构那样。

 

4.1 Immutable for Object

4.2 concat for Array

4.3 filter for Array

4.4 定制化 硬拷贝

详情请见: [React] 11 - Redux: redux

二、教程学习

React JS Tutorials - Redux部分

 

  • starter 框架

import { createStore } from "redux";


(2) 接收到动作信号

const reducer = (initialState=0, action) => {

  ...

}

const store = createStore(reducer, 1)



(3) 触发事件
/* 每次 state 改变的时候会执行一次这个函数 */

store.subscribe(() => {

console.log("store changed", store.getState());

})


(1) 发生一次用户动作

// store.dispatch({type: "INC"})


添加了更多的动作,触发了更多的事件。

 

 

  • combineReducers 框架

Jeff: 多reducer的情况的处理方式。

import { combineReducers, createStore } from "redux";


///////////////////////////////////////////////////////////////////////////////////////////////////////

/**
* 放在单独的文件里为好
*/
const userReducer = (state={}, action) => {

switch(action.type) {

case "SET_NAME": {

return {...state, name: action.payload};

break;

}

case "SET_AGE": {

return {...state, age: action.payload};

break;

}

}

return state;

}

// I would live in a separate file

const tweetsReducer = (state=[], action) => {

switch(action.type) {

case "ADD_TWEET": {

return state.concat({

id: Date.now(), //fake an ID by using a timestamp

text: action.payload,

});

break;

}

}

return state;

}

-------------------------------------------------------------------------------------------------------

const reducers = combineReducers({

user: userReducer,

tweets: tweetsReducer

})

////////////////////////////////////////////////////////////////////////////////////////////////////////

const store = createStore(reducers)  // state的默认值可以作为第二个参数,或者放在reducer的第一个init state定义


////////////////////////////////////////////////////////////////////////////////////////////////////////
store.subscribe(() => {

console.log("store changed", store.getState());

})

////////////////////////////////////////////////////////////////////////////////////////////////////////

store.dispatch({type: "SET_NAME", payload: "Will"})

store.dispatch({type: "SET_AGE", payload: 35})

store.dispatch({type: "SET_AGE", payload: 34})

store.dispatch({type: "ADD_TWEET", payload: "OMG LIKE LOL"})

store.dispatch({type: "ADD_TWEET", payload: "I am so like seriously like totally like right now"})

请注意:作为对比,上例中的store.dispatch一般会写在onClick中,由用户触发,如下。

const render = () => ReactDOM.render(

<Counter

value={store.getState()}

onIncrement={() => store.dispatch({ type: 'INCREMENT' })}

onDecrement={() => store.dispatch({ type: 'DECREMENT' })}

/>,

rootEl

)

 

 

  • Middleware 框架

详见请见:[React] 12 - Redux: async & middleware

  • React-redux 框架

详见请见:[React] 13 - Redux: react-redux

  • React-saga 框架

详情可见::[React] 14 - Redux: Redux Saga

 

End.

以上是 [Full-stack] 前端框架鼻祖 - React.js 的全部内容, 来源链接: utcz.com/z/381294.html

回到顶部