痛点:前端html的开发越来越复杂,没有可重用和模块化编程
-
React
不是一个完整的MVC
,MVVM
框架,它只负责View
层 - 基于
Virtual DOM
思想
0x01 JSX & Components
- 类似于
DOM
结构的对象,html
标签默认小写,自定义的组件(Components
)首字母大写 -
jsx
中需要执行js
表达式时,需要使用中括号({}
)包起来 -
jsx
的样式不能使用class
表示,因为在js
中class
是关键字,需要使用clasName
表示 -
jsx
内的style
需要使用对象来表示,属性使用驼峰式命名规则
// 默认jsx
<div> Hello Word! </div>
// 自定义组件
<Hello className='xxxx' style={{color:'red',fontSize:'20px'}} name='xxx' />
state & props
-
props
是组件调用方在调用组件时传入的,props
一旦指定一般不会改变,在结构上,props
也是属于调用方 -
state
是私属于组件方,state
是可变的,即可以理解为函数内部状态参数。通过Component
的setState
方法来修改state
的值 -
state
是组件内部的状态,state
变化的时候会重新render
componentDisMount : function () {
var _self = this;
// 为什么要对this重新赋值?
// 因为在window.setTimeout()函数参数之外this指的就是当前的component,
// 回调函数中的this指的就是最外边的全局对象:global或window
// this的4中用法:
// 1:构造函数内部this,指的是创建的对象
function cst(){
this.x=1;
};
var t1 = new cst();
// 2:call apply bind中使用,传入的this替换函数内部的this
// 3:this指的是调用函数的那个对象
// 4:this出现在setTimeout函数的函数内部时,this指的是全局对象
window.setTimeout(function () {
_self.setState({
fontSize:'40px',
});
}, 1000);
}
context
- 什么场景下使用
context
:当你不想每层组件都手动传递需要的props的时候 - 官方建议不要使用
context
,用props & state
代替控制数据流 - 官方详细参考资料
使用
props
传递color
属性
class Button extends React.Component {
render() {
return (
<button style={{background: this.props.color}}>
{this.props.children}
</button>
);
}
}
class Message extends React.Component {
render() {
return (
<div>
{this.props.text} <Button color={this.props.color}>Delete</Button>
</div>
);
}
}
class MessageList extends React.Component {
render() {
const color = "purple";
const children = this.props.messages.map((message) =>
<Message text={message.text} color={color} />
);
return <div>{children}</div>;
}
}
使用
context
,我们可以自动地在组件树中传递参数。
const PropTypes = require('prop-types');
class Button extends React.Component {
render() {
return (
<button style={{background: this.context.color}}>
{this.props.children}
</button>
);
}
}
Button.contextTypes = {
color: PropTypes.string
};
class Message extends React.Component {
render() {
return (
<div>
{this.props.text} <Button>Delete</Button>
</div>
);
}
}
class MessageList extends React.Component {
getChildContext() {
return {color: "purple"};
}
render() {
const children = this.props.messages.map((message) =>
<Message text={message.text} />
);
return <div>{children}</div>;
}
}
MessageList.childContextTypes = {
color: PropTypes.string
};
通过在
MessageList
(context
提供者)中添加childContextTypes
和getChildContext
,React
会向下自动传递参数,任何组件只要在它的子组件中(这个例子中是Button
),就能通过定义contextTypes
来获取参数。
如果contextTypes
没有定义,那么context
将会是个空对象。
0x02 React Components Lifecycle
Lifecyclemounted
-
React Components
被Render
解析生成对应的DOM
节点并被插入到浏览器DOM
结构的过程,即当我们在浏览器上可以看到组件的时候,Mounted
已经结束了
update
- 一个
Mounted
的React Components
被重新Render
的过程,差量变更 -
4
个hook
函数一般不进行修改
unmounted
- 一个
Mounted
的React Components
对应的DOM
节点被从浏览器DOM
结构中移除的过程
0x03 React Event Listener
-
React Component
上绑定事件,采用驼峰式来绑定事件
0x04 When the project starts to use
yeoman
- 用来生成项目代码结构,根据自己选择架构,自动生成项目结构
webpack
- 相当于盖好了房子给用户来使用,
grunt
等工具则是需要用户自己来盖房子 -
Webpack
本身只能处理JavaScript
模块,如果要处理其他类型的文件,就需要使用loader
进行转换。将css
文件转换成style
属性绑定到DOM
上,将图片等url
也加到对应的位置
0x05 Redux
-
React
是纯View
层框架,需要搭配数据流才能进行完整的前端开发,这个数据流就是Redux
- 目前的数据流框架:
Flux
、reFlux
、Redux
-
Redux
简答、单一状态树
action
- 普通的
JS
对象Objects
- 一般由方法生成
- 必须有
type
属性
const addTodo = (text) => {
return {
type : "ADD_TODO",
id : nextTodoId++,
text
}
}
reducer
- 也由方法生成
- 生成它的方法是一个纯函数(纯方法就是没有任何外界依赖,
1+1=2
这样的必然性 ) - 传入旧的
state
,返回新的state
const todo = (state, action) => {
switch (action.type) {
case "ADD_TODO":
return {
id : action.id,
text : action.text,
completed : false
}
default:
return state
}
}
store
-
store
包含所有的reducer
-
action
作用于store
上,而不是直接作用于reducer
上 -
reducer
根据store
做响应 -
state
和reducer
的混合体 -
store
是唯一的 - 包括了完整的
state
-
state
完全可预测
react-redux
- 项目中可以直接使用
redux
,也可以使用react-redux
,后者提供了便捷的API
- 组件需要分成两类:
UI component
和container component
,UI
组件展示样式,container
组件处理业务逻辑、数据和状态 - 如果一个组件既有
UI
又有业务逻辑怎么办?将它拆分成下面的结构:外面是一个容器组件,里面包了一个UI
组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图。 -
connect()
用于从UI
组件生成容器组件。connect
的意思,就是将这两种组件连起来。
下面代码中,
TodoList
是UI
组件,VisibleTodoList
就是由React-Redux
通过connect
方法自动生成的容器组件。
import { connect } from 'react-redux'
const VisibleTodoList = connect()(TodoList);
但是,因为没有定义业务逻辑,上面这个容器组件毫无意义,只是
UI
组件的一个单纯的包装层。为了定义业务逻辑,需要给出下面两方面的信息。
(1)输入逻辑:外部的数据(即state
对象)如何转换为UI
组件的参数
(2)输出逻辑:用户发出的动作如何变为Action
对象,从UI
组件传出去。
因此,connect
方法的完整API
如下。
import { connect } from 'react-redux'
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
上面代码中,
connect
方法接受两个参数:mapStateToProps
和mapDispatchToProps
。它们定义了UI
组件的业务逻辑。前者负责输入逻辑,即将state
映射到UI
组件的参数(props
),后者负责输出逻辑,即将用户对UI
组件的操作映射成Action
。
mapStateToProps()
- 它的作用就是像它的名字那样,建立一个从(外部的)
state
对象到(UI
组件的)props
对象的映射关系 - 函数返回一个对象,里面的每一个键值对就是一个映射
下面代码中,
mapStateToProps
是一个函数,它接受state
作为参数,返回一个对象。这个对象有一个todos
属性,代表UI
组件的同名参数,后面的getVisibleTodos
也是一个函数,可以从state
算出todos
的值。
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
下面就是
getVisibleTodos
的一个例子,用来算出todos
。
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'SHOW_ALL':
return todos
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
default:
throw new Error('Unknown filter: ' + filter)
}
}
mapStateToProps
会订阅Store
,每当state
更新的时候,就会自动执行,重新计算UI
组件的参数,从而触发UI
组件的重新渲染。
mapStateToProps
的第一个参数总是state
对象,还可以使用第二个参数,代表容器组件的props
对象。
// 容器组件的代码
// <FilterLink filter="SHOW_ALL">
// All
// </FilterLink>
const mapStateToProps = (state, ownProps) => {
return {
active: ownProps.filter === state.visibilityFilter
}
}
使用
ownProps
作为参数后,如果容器组件的参数发生变化,也会引发UI
组件重新渲染。
connect
方法可以省略mapStateToProps
参数,那样的话,UI
组件就不会订阅Store
,就是说Store
的更新不会引起UI
组件的更新。
mapDispatchToProps()
mapDispatchToProps
是connect
函数的第二个参数,用来建立UI
组件的参数到store.dispatch
方法的映射。也就是说,它定义了哪些用户的操作应该当作Action
,传给Store
。它可以是一个函数,也可以是一个对象。
如果mapDispatchToProps
是一个函数,会得到dispatch
和ownProps
(容器组件的props
对象)两个参数。
const mapDispatchToProps = (
dispatch,
ownProps
) => {
return {
onClick: () => {
dispatch({
type: 'SET_VISIBILITY_FILTER',
filter: ownProps.filter
});
}
};
}
从上面代码可以看到,
mapDispatchToProps
作为函数,应该返回一个对象,该对象的每个键值对都是一个映射,定义了UI
组件的参数怎样发出Action
。
如果mapDispatchToProps
是一个对象,它的每个键名也是对应UI
组件的同名参数,键值应该是一个函数,会被当作Action creator
,返回的Action
会由Redux
自动发出。举例来说,上面的mapDispatchToProps
写成对象就是下面这样。
const mapDispatchToProps = {
onClick: (filter) => {
type: 'SET_VISIBILITY_FILTER',
filter: filter
};
}
<Provider> 组件
connect
方法生成容器组件以后,需要让容器组件拿到state
对象,才能生成UI
组件的参数。一种解决方法是将
state
对象作为参数,传入容器组件。但是,这样做比较麻烦,尤其是容器组件可能在很深的层级,一级级将state
传下去就很麻烦。
React-Redux
提供Provider
组件,可以让容器组件拿到state
。
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'
let store = createStore(todoApp);
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
上面代码中,
Provider
在根组件外面包了一层,这样一来,App
的所有子组件就默认都可以拿到state
了。它的原理是
React
组件的context
属性,请看源码。
class Provider extends Component {
getChildContext() {
return {
store: this.props.store
};
}
render() {
return this.props.children;
}
}
Provider.childContextTypes = {
store: React.PropTypes.object
}
上面代码中,
store
放在了上下文对象context
上面。然后,子组件就可以从context
拿到store
,代码大致如下。
class VisibleTodoList extends Component {
componentDidMount() {
const { store } = this.context;
this.unsubscribe = store.subscribe(() =>
this.forceUpdate()
);
}
render() {
const props = this.props;
const { store } = this.context;
const state = store.getState();
// ...
}
}
VisibleTodoList.contextTypes = {
store: React.PropTypes.object
}
React-Redux
自动生成的容器组件的代码,就类似上面这样,从而拿到store
。
redux-saga
- 在
saga
的Middleware
中,可以使用takeEvery
或者takeLatest
等API
来监听某个action
- 当某个
action
触发后,saga
可以使用call
、fetch
等api
发起异步操作,操作完成后使用put
函数触发action
,同步更新state
,从而完成整个State
的更新
0x06 React-router
-
react
原生的路由可以满足简单的应用,复杂场景使用起来非常复杂 - 官方参考文档
不使用
react-router
的情况
import React from 'react'
import { render } from 'react-dom'
const About = React.createClass({/*...*/})
const Inbox = React.createClass({/*...*/})
const Home = React.createClass({/*...*/})
const App = React.createClass({
getInitialState() {
return {
route: window.location.hash.substr(1)
}
},
componentDidMount() {
window.addEventListener('hashchange', () => {
this.setState({
route: window.location.hash.substr(1)
})
})
},
render() {
let Child
switch (this.state.route) {
case '/about': Child = About; break;
case '/inbox': Child = Inbox; break;
default: Child = Home;
}
return (
<div>
<h1>App</h1>
<ul>
<li><a href="#/about">About</a></li>
<li><a href="#/inbox">Inbox</a></li>
</ul>
<Child/>
</div>
)
}
})
React.render(<App />, document.body)
上述代码使用
this.state.route
来动态监控URL HASH部分的变化,从而渲染不同的<Child>
组件
使用
react-router
的情况
import React from 'react'
import { render } from 'react-dom'
// 首先我们需要导入一些组件...
import { Router, Route, Link } from 'react-router'
// 然后我们从应用中删除一堆代码和
// 增加一些 <Link> 元素...
const App = React.createClass({
render() {
return (
<div>
<h1>App</h1>
{/* 把 <a> 变成 <Link> */}
<ul>
<li><Link to="/about">About</Link></li>
<li><Link to="/inbox">Inbox</Link></li>
</ul>
{/*
接着用 `this.props.children` 替换 `<Child>`
router 会帮我们找到这个 children
*/}
{this.props.children}
</div>
)
}
})
// 最后,我们用一些 <Route> 来渲染 <Router>。
// 这些就是路由提供的我们想要的东西。
React.render((
<Router>
<Route path="/" component={App}>
<Route path="about" component={About} />
<Route path="inbox" component={Inbox} />
</Route>
</Router>
), document.body)
0xFF 问题总结:
- 拿不到组件的
refs
引用?
组件显示了以后才能拿到组件的
refs
引用,因为refs
拿的是真是DOM
,虚拟DOM
真正插入到HTML DOM
的时候才能拿到refs
显示以后,也不一定可以对内部的参数进行操作,需要delay()
或者setTimeout()
进行延迟操作
欢迎关注微信公众号(coder0x00)或扫描下方二维码关注,我们将持续搜寻程序员必备基础技能包提供给大家。
网友评论