monsterooo / blog Goto Github PK
View Code? Open in Web Editor NEWand make promises by the hours
License: MIT License
and make promises by the hours
License: MIT License
我同意@AlexZhong22c的说法。
- 这个依赖是要写的,useCallback只是说当依赖不变的时候,返回同一个函数,但是不能省略依赖的
- useCallback应该是说避免了其他的state对当前函数的影响。这个count的依赖项我认为是必须要的。
- 另外呢,还可以用useReducer去维护一个局部的redux,dispatch去做,见useReducer
我看错了, setCount(count => count + 1) setCount是个函数,可以拿到最新的值。相当于以前的setState(prevState => prevState.count + 1)。
不过,useCallback(() => setCount(count => count + 1), [])第二个参数写空数组不合适的吧,毕竟官方文档描述认为函数中引用的项都应该出现在依赖中
最佳实践还是按照官方的来没问题,因为那个比较适合大多数情况。
像我们讨论的这种情况(优化依赖和缓存函数),不是应用特别大或者组件特别多的时候不会有太大的问题。
但是如果我们在写代码之前就知道并且做一些优化,对于以后可能会出现的性能问题就会有所避免。
Originally posted by @monsterooo in #37 (comment)
react 推荐的官方插件,会强制给你把count给你添加上,还有一点gif图,如果不开全局代理,gif图都挂了
收集不常见的JavaScript语法
✍️
React DnD
GitHub - mzabriskie/react-draggable: React draggable component
DnD在内部使用Redux保存数据
后端(Backends):
在DnD中你可以使用可插拔的方法去实现不同平台的拖动功能,在web上一般使用HTML5的drag drop,这种可插拔的方式在DnD中叫做后端。
项目和类型(Items and Types):
在DnD中使用数据而不是视图作为真实的来源,当在屏幕上拖动某些内容的时候,我们不会说正在拖动组件或DOM节点。相反,我们说某个类型(type)的项目(item)正在被拖动。
什么是项目(item)?项目(item)是一个简单的javascript对象,用于描述被拖动的内容。例如在看板应用程序中,当你拖动卡片是,项目可能看起来像{cardId: 42}
。将拖动的数据描述为普通的对象有助于保持组件分离并且彼此不知道
DnD通过称为监视器(monitors)的内部状态存储上的几个小包装器将此状态公开给组件。监视器允许你更新组件的poprs以相应拖动状态更改。
假设你想在拖动棋子时突出显示象棋单元格。Cell组件的收集函数(collecting function)可能就像下面这样:
function collect(monitor) {
return {
highlighted: monitor.canDrop(),
hovered: monitor.isOver(),
}
}
它指示DnD将突出显示的最新值作为props传递给所有Cell实例
实际上,连接器(connectors)作为我们上面描述的收集函数的第一个参数传递。让我们来看看如何使用它来指定放置目标
// collect函数的第一个参数就是连接器
function collect(connect, monitor) {
return {
highlighted: monitor.canDrop(),
hovered: monitor.isOver(),
connectDropTarget: connect.dropTarget(),
}
}
在组件render
方法中,我们即可以访问从监视器(monitor)获得的数据和从连接器(connector)获得的函数:
render() {
const { highlighted, hovered, connectDropTarget } = this.props;
return connectDropTarget(
<div className={classSet({
'Cell': true,
'Cell--highlighted': highlighted,
'Cell--hovered': hovered
})}>
{this.props.children}
</div>
);
}
connectDropTarget
调用告诉DnD我们的根DOM节点是一个有效的放置(drop)目标,并且它的hover和drop事件应该由后端处理。在内部,它通过将一个回调引用附加到你给它的React元素来工作。连接器返回的函数是memoized
,因此它不会破坏shouldComponentUpdate
优化。
拖动源和放置目标(Drag Sources and Drop Targets)
每当你想要使组件或其某部分可拖动时,你需要将该组件包装到拖动源(drag source)申明中。每个拖动都注册了某种类型(type),并且必须实现从组件的props中生成项目(item)的方法。它还可以选择指定一些其他方法来处理拖放时间。拖动源(drag source)声明还允许你指定给定组件的收集函数(collecting function)
高阶组件(Higher-Order)和装饰器(Decorators)
你如何包装组件?包装是什么意思?如果你以前没有使用过高阶组件,请继续阅读本文,因为它详细解释了这个概念。
高阶组件组件只是一个函数,它接收一个React组件,并返回另一个React组件类。库提供的包装组件在其render
方法中呈现组将并将props
转发给它,但也添加了一些有用的行为。
在DnD中,DragSource
和DragTarget
以及一些其他顶级导出函数实际上是高阶组件。它们向组件中注入拖放魔法。
使用它们的一个提示是,它们必须需要两个函数。例如,一下是如何在DragSource
中包装YourComponent
import { DragSource } from 'react-dnd'
class YourComponent {
/* ... */
}
export default DragSource(/* ... */)(YourComponent)
请注意,在第一个函数调用中指定DragSource
参数之后,还有第二个函数调用,第二个函数调用中传递你的类(组件)。这叫做currying
或partial application
,并且是装饰器语法开箱即用的必要条件:
import { DragSource } from 'react-dnd'
@DragSource(/* ... */)
export default class YourComponent {
/* ... */
}
你不需要使用这种装饰器语法,如果你喜欢它,你可以使用Babel来转换你的代码,并将{ "stage": 1 }
放入.babelrc文件来启用它。
即使你不打算使用装饰器,部分应用程序仍然可以使用,因为你可以使用如_.flow
在javascript中组合多个DragSource
和DropTarget
声明。
import { DragSource, DropTarget } from 'react-dnd'
import flow from 'lodash/flow'
class YourComponent {
render() {
const { connectDragSource, connectDropTarget } = this.props
return connectDragSource(
connectDropTarget(),
/* ... */
)
}
}
export default flow(
DragSource(/* ... */),
DropTarget(/* ... */),
)(YourComponent)
import React from 'react'
import { DragSource } from 'react-dnd'
// Drag sources和drag targets只有在具有相同的字符串类型是才交互
const Types = {
CARD: 'card',
}
/**
* 指定drag source 合约
* 只需要 `beginDrag` 功能
*/
const cardSource = {
beginDrag(props) {
// 返回描述拖动项目(item)的数据
const item = { id: props.id }
return item
},
endDrag(props, monitor, component) {
if (!monitor.didDrop()) {
return
}
// 做一些拖动操作处理
const item = monitor.getItem()
const dropResult = monitor.getDropResult()
CardActions.moveCardToList(item.id, dropResult.listId)
},
}
/**
* 指定要注入到组件的props
*/
function collect(connect, monitor) {
return {
// 在render()中调用此函数,让DnD处理拖动事件
connectDragSource: connect.dragSource(),
// 你还可以通过监视器获取当前拖动状态
isDragging: monitor.isDragging(),
}
}
function Card(props) {
// 你的组件可以像以前一样收到props
const { id } = props
// 这两个props由DnD注入,由上面的`collect`函数定义
const { isDragging, connectDragSource } = props
return connectDragSource(
<div>
I am a draggable card number {id}
{isDragging && ' (and I am being dragged now)'}
</div>,
)
}
// 导出包装的版本
export default DragSource(Types.CARD, cardSource, collect)(Card)
本章预览部分完。
withhandlers
接收一个对象
或函数
为其接下来的组件创建事件处理函数,每个事件处理函数都接收一个参数,这个参数就是组件的props
。
withHandlers(
handlerCreators: {
[handlerName: string]: (props: Object) => Function
} |
handlerCreatorsFactory: (initialProps) => {
[handlerName: string]: (props: Object) => Function
}
): HigherOrderComponent
实际调用也就是两种形式
// 1
withHandlers({
handleClick: props => event => {
alert(props.title)
}
})
// 2
withHandlers(() => {
return {
handleClick: props => event => {
console.log('我被点击了', props);
},
};
})
const { compose, withHandlers } = Recompose;
const ButtonEnhance = compose(
withHandlers({
handleClick: props => event => {
alert('我被点击了, ' + props.title)
}
})
)(({ handleClick }) => (
<button onClick={handleClick}>点我~~</button>
))
在codepen在线预览
renderComponent
用于包装一个组件为高阶函数,当一个组件需要成为高阶函数和其他高阶函数组合时比较有用,比如和branch
组合
renderComponent(
Component: ReactClass | ReactFunctionalComponent | string
): HigherOrderComponent
const { compose, branch, renderComponent } = Recompose;
const Bar1 = () => (<p>Bar1</p>);
const Bar2 = () => (<p>Bar2</p>);
const Foo = compose(
branch(
props => props.isShow,
renderComponent(Bar1),
renderComponent(Bar2),
),
)(({ title }) => (
<div>{ title }</div>
))
在codepen在线预览
我同意@AlexZhong22c的说法。
- 这个依赖是要写的,useCallback只是说当依赖不变的时候,返回同一个函数,但是不能省略依赖的
- useCallback应该是说避免了其他的state对当前函数的影响。这个count的依赖项我认为是必须要的。
- 另外呢,还可以用useReducer去维护一个局部的redux,dispatch去做,见useReducer
我看错了, setCount(count => count + 1) setCount是个函数,可以拿到最新的值。相当于以前的setState(prevState => prevState.count + 1)。
不过,useCallback(() => setCount(count => count + 1), [])第二个参数写空数组不合适的吧,毕竟官方文档描述认为函数中引用的项都应该出现在依赖中
最佳实践还是按照官方的来没问题,因为那个比较适合大多数情况。
像我们讨论的这种情况(优化依赖和缓存函数),不是应用特别大或者组件特别多的时候不会有太大的问题。
但是如果我们在写代码之前就知道并且做一些优化,对于以后可能会出现的性能问题就会有所避免。
Originally posted by @monsterooo in #37 (comment)
react 推荐的官方插件,会强制给你把count给你添加上
withProps
接受一个函数参数,这个函数参数会返回一个对象用作为接下来的组件的props
。与mapProps
不同的是,除了withProps
函数参数返回的props
外,组件接收到的其他参数也将一起被传递
withProps(
createProps: (ownerProps: Object) => Object | Object
): HigherOrderComponent
const ListMap = withProps(({ list }) => {
return {
list: list.map((e) => e + '_withProps')
};
})(List);
// title 也会被一同传递到List组件中
<ListMap list={Item} title="我是一个标题文本!" />
在codepen在线预览
mapProps
函数接收一个函数参数,这个函数参数会返回一个对象用作为接下来的组件的props
。组件接收到的props
只能是通过mapProps
函数参数返回的对象,其他的props
将会被忽略
const Item = ['a', 'b', 'c', 'd'];
const ListMap = mapProps(({ list }) => {
return {
list: list.map((e) => e + '_extends')
};
})(List);
现在可以调用ListMap
组件,并且可以给它传递一个list
属性<ListMap list={Item} />
,在List
组件中获取到的list
数组值每个后缀都会被加上_extends
字符。并且如果你还有其他额外的props
传入会被过滤掉比如<ListMap list={Item} title="hello"/>
,在List组件中并不会接收到title
属性。
在codepen在线预览
练习地址: https://coderlane.net/s
function Solution() {
var arr = [5,3,1,8,7,0,2,6];
var N = arr.length;
for(var i = 0; i < N; i++) {
var minIdx = i;
for(var j = i + 1; j < N; j++) {
if (arr[j] < arr[minIdx]) { // 选择最小的元素
minIdx = j;
}
}
exch(arr, i, minIdx);
}
console.log('排序完成:', arr)
}
function exch(arr, i, j) {
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
Solution();
function Solution() {
var arr = [1,7,2,4,8,0];
var N = arr.length;
for(var i = 1; i < N; i++) {
var cur = arr[i];
var j = i - 1;
while(j >= 0 && arr[j] > cur) {
arr[j+1] = arr[j];
--j;
}
arr[j+1] = cur;
}
console.log('插入排序方式1', arr)
}
Solution();
renderNothing
和它的名字一样,它是一个返回null
的高阶组件
renderNothing: HigherOrderComponent
const { compose, branch, renderNothing } = Recompose;
const Bar = () => (<p>Bar</p>);
const Foo = compose(
branch(
props => props.isShow,
() => Bar,
renderNothing,
),
)(({ title }) => (
<div>{ title }</div>
))
class App extends React.Component {
render() {
return (
<div>
什么组件也不展示
<Foo isShow={false} />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'))
在codepen在线预览
官方解释
返回一个函数,这个函数可以产生你给定类型的React元素。像React.createElement(),参数类型可以是div、span、一个React Component(类或函数),或是一个React.fragment
官方说的有点含糊并且也没有提供一些代码示例,下面就来总结一下这两个的使用方法和区别
React.createElement(
type,
[props],
[...children]
)
这个函数用法很明显它创建一个React元素,并且接收三个参数。第一个参数为类型包括html元素类型和React Component或React fragment。第二个参数是元素的属性对应的是html的属性,第三个参数为子元素,可以为一个字符串。也可以是React Component
React.createFactory(type)
createFactory底层还是调用了createElement,不同的是createFactory返回的是一个函数。它不会立刻调用createElement。而是在你调用这个返回函数的时候创建元素(HOC)
举个例子
const factory = React.createFactory('div');
const TestCreateFactory = factory(null, 'div create factory');
上面factory函数是调用createFactory返回的一个函数,调用factory函数创建的所有组件必然是一个html的div元素,factory(null, 'div create factory')
可以为创建的div元素指定属性和子元素。
总结
React.createElement立刻创建一个React组件
React.createFactory返回一个工厂函数,调用它可以创建一系列相同的组件。
withState
可以为组件增加一个state
和一个set state
的函数,withState
接收三个参数,第一个参数为state name
,第二个参数是set state
的函数名,第三个参数是一个初始值。
set state
函数有两种调用方式,具体类型如下:
stateUpdater<T>((prevValue: T) => T, ?callback: Function): void
stateUpdater(newValue: any, ?callback: Function): void
withState(
stateName: string,
stateUpdaterName: string,
initialState: any | (props: Object) => any
): HigherOrderComponent
const { compose, withState } = Recompose;
const Foo = compose(
withState('title', 'setTitle', "我是默认字符串!")
)(({ title, setTitle }) => (
<div>
<p>{title}</p>
<button onClick={() => setTitle('Hello,World!')}>点击我更改标题</button>
<button onClick={() => setTitle((prevValue) => `${prevValue},Hello,World!`)}>点击我也可以更改标题</button>
</div>
))
withState
最好与withHandlers
一起使用,避免创建() => setTitle('Hello,World!')
这样的匿名函数
在codepen在线预览
Render Props
模式是一种非常灵活复用性非常高的模式,它可以把特定行为或功能封装成一个组件,提供给其他组件使用让其他组件拥有这样的能力,接下来我们一步一步来看React组件中如何实现这样的功能。
React
中我们可以给一个组件传递一些props
并且在组件内部展示,同样的我们也可以传递一些组件同样也是行得通的,一起看一个例子
我们可以通过组件传递一些字符串数据,并且在组件内部渲染
下面的代码很平常,我们绝大多数代码都是这样。
const Foo = ({ title }) => (
<div>
<p>{title}</p>
</div>
);
class App extends React.Component {
render() {
return (
<div>
<h2>这是一个示例组件</h2>
<Foo title="大家好,我是土豆" />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'))
更进一步,我们可以在组件上传递普通的HTML 标签
和React 组件
达到复用的目的
// https://codepen.io/tudou/full/OvdrPW
const Bar = () => (<p>我是Bar组件 :)</p>);
const Foo = ({ title, component }) => (
<div>
<p>{title}</p>
{component()}
</div>
);
class App extends React.Component {
render() {
return (
<div>
<h2>这是一个示例组件</h2>
<Foo title={<p>大家好,我是土豆</p>} component={() => <Bar /> } />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'))
在上面的例子中传递普通的HTML 标签
对我们复用组件没有任何帮助,重点可以看传递component
这个参数,它传递给Foo
组件一个函数这个函数返回的是一个Bar 组件
,我们会在Foo 组件
中调用并且显示component
函数中的组件。我们再来写一个小的DEMO进行验证。
Render Props
例子// https://codepen.io/tudou/full/dmawvY
const Bar = ({ title }) => (<p>{title}</p>);
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = { title: '我是一个state的属性' };
}
render() {
const { render } = this.props;
const { title } = this.state;
return (
<div>
{render(title)}
</div>
)
}
}
class App extends React.Component {
render() {
return (
<div>
<h2>这是一个示例组件</h2>
<Foo render={(title) => <Bar title={title} />} />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'))
在上面的例子中,给Foo 组件
传递了一个render
参数它是一个函数这个函数返回一个Bar
组件,这个函数接受一个参数title
他来自于Foo 组件
调用时传递并且我们又将title 属性
传递给了Bar 组件
。经过上述的调用过程我们的Bar 组件
就可以共享到Foo 组件内部的
state 属性`。
children
传递这个demo略微不同于上面通过props
传递,而它是通过组件的children
传递一个函数给Foo 组件
// https://codepen.io/tudou/full/WzPPeL
const Bar = ({ title }) => (<p>{title}</p>);
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = { title: '我是一个state的属性' };
}
render() {
const { children } = this.props;
const { title } = this.state;
return (
<div>
{children(title)}
</div>
)
}
}
class App extends React.Component {
render() {
return (
<div>
<h2>这是一个示例组件</h2>
<Foo>
{(title) => (
<Bar title={title} />
)}
</Foo>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'))
观察可发现只是写法略微有些变法,我们将要传递的数据放到的组件的children
。实际上并无不同之处(都是传递一个函数)
<Foo>
{(title) => (
<Bar title={title} />
)}
</Foo>
请注意当我们的Foo 组件
继承于React.PureComponent
的时候,我们需要避免下面这样的写法。不然我们的性能优化将付之东流。
render() {
return (
<div>
<h2>这是一个示例组件</h2>
<Foo render={(title) => <Bar title={title} />} />
</div>
);
}
如果你在render
创建一个函数,在每次渲染的时候render prop
将会是一个新的值,那么每次将会重新渲染Bar
。
正确的做法应该是在组件内部创建一个函数用于显示组件
const Bar = ({ title }) => (<p>{title}</p>);
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = { title: '我是一个state的属性' };
}
render() {
const { render } = this.props;
const { title } = this.state;
return (
<div>
{render(title)}
</div>
)
}
}
class App extends React.Component {
// 单独创建一个渲染函数
renderFoo(title) {
return <Bar title={title} />;
}
render() {
return (
<div>
<h2>这是一个示例组件</h2>
<Foo render={this.renderFoo} />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'))
学习了解Render Props
渲染模式原理,使用了render
和children
两种不同的渲染方法。更新详细的官方例子请参考 https://reactjs.org/docs/render-props.html
官方例子在线参考 https://codesandbox.io/embed/1075p1yov3
谢谢阅读
在本文中,你将学习如何使用Node.js中的async函数(async/await)来简化callback或Promise.
异步语言结构在其他语言中已经存在了,像c#的async/await、Kotlin的coroutines、go的goroutines,随着Node.js 8的发布,期待已久的async函数也在其中默认实现了。
当函数声明为一个Async函数它会返回一个AsyncFunction
对象,它们类似于Generator
因为执可以被暂停。唯一的区别是它们返回的是Promise
而不是{ value: any, done: Boolean }
对象。不过它们还是非常相似,你可以使用co包来获取同样的功能。
在async函数中,可以等待Promise
完成或捕获它拒绝的原因。
如果你要在Promise中实现一些自己的逻辑的话
function handler (req, res) {
return request('https://user-handler-service')
.catch((err) => {
logger.error('Http error', err)
error.logged = true
throw err
})
.then((response) => Mongo.findOne({ user: response.body.user }))
.catch((err) => {
!error.logged && logger.error('Mongo error', err)
error.logged = true
throw err
})
.then((document) => executeLogic(req, res, document))
.catch((err) => {
!error.logged && console.error(err)
res.status(500).send()
})
}
可以使用async/await
让这个代码看起来像同步执行的代码
async function handler (req, res) {
let response
try {
response = await request('https://user-handler-service')
} catch (err) {
logger.error('Http error', err)
return res.status(500).send()
}
let document
try {
document = await Mongo.findOne({ user: response.body.user })
} catch (err) {
logger.error('Mongo error', err)
return res.status(500).send()
}
executeLogic(document, req, res)
}
在老的v8版本中,如果有有个promise
的拒绝没有被处理你会得到一个警告,可以不用创建一个拒绝错误监听函数。然而,建议在这种情况下退出你的应用程序。因为当你不处理错误时,应用程序处于一个未知的状态。
process.on('unhandledRejection', (err) => {
console.error(err)
process.exit(1)
})
在处理异步操作时,有很多例子让他们就像处理同步代码一样。如果使用Promise
或callbacks
来解决问题时需要使用很复杂的模式或者外部库。
当需要再循环中使用异步获取数据或使用if-else
条件时就是一种很复杂的情况。
使用Promise
实现回退逻辑相当笨拙
function requestWithRetry (url, retryCount) {
if (retryCount) {
return new Promise((resolve, reject) => {
const timeout = Math.pow(2, retryCount)
setTimeout(() => {
console.log('Waiting', timeout, 'ms')
_requestWithRetry(url, retryCount)
.then(resolve)
.catch(reject)
}, timeout)
})
} else {
return _requestWithRetry(url, 0)
}
}
function _requestWithRetry (url, retryCount) {
return request(url, retryCount)
.catch((err) => {
if (err.statusCode && err.statusCode >= 500) {
console.log('Retrying', err.message, retryCount)
return requestWithRetry(url, ++retryCount)
}
throw err
})
}
requestWithRetry('http://localhost:3000')
.then((res) => {
console.log(res)
})
.catch(err => {
console.error(err)
})
代码看的让人很头疼,你也不会想看这样的代码。我们可以使用async/await重新这个例子,使其更简单
function wait (timeout) {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, timeout)
})
}
async function requestWithRetry (url) {
const MAX_RETRIES = 10
for (let i = 0; i <= MAX_RETRIES; i++) {
try {
return await request(url)
} catch (err) {
const timeout = Math.pow(2, i)
console.log('Waiting', timeout, 'ms')
await wait(timeout)
console.log('Retrying', err.message, i)
}
}
}
上面代码看起来很舒服对不对
不像前面的例子那么吓人,如果你有3个异步函数依次相互依赖的情况,那么你必须从几个难看的解决方案中进行选择。
functionA
返回一个Promise
,那么functionB
需要这个值而functioinC
需要functionA
和functionB
完成后的值。
then
圣诞树function executeAsyncTask () {
return functionA()
.then((valueA) => {
return functionB(valueA)
.then((valueB) => {
return functionC(valueA, valueB)
})
})
}
用这个解决方案,我们在第三个then
中可以获得valueA
和valueB
,然后可以向前面两个then
一样获得valueA
和valueB
的值。这里不能将圣诞树(毁掉地狱)拉平,如果这样做的话会丢失闭包,valueA
在functioinC
中将不可用。
function executeAsyncTask () {
let valueA
return functionA()
.then((v) => {
valueA = v
return functionB(valueA)
})
.then((valueB) => {
return functionC(valueA, valueB)
})
}
在这颗圣诞树中,我们使用更高的作用域保变量valueA
,因为valueA
作用域在所有的then
作用域外面,所以functionC
可以拿到第一个functionA
完成的值。
这是一个很有效扁平化.then
链"正确"的语法,然而,这种方法我们需要使用两个变量valueA
和v
来保存相同的值。
function executeAsyncTask () {
return functionA()
.then(valueA => {
return Promise.all([valueA, functionB(valueA)])
})
.then(([valueA, valueB]) => {
return functionC(valueA, valueB)
})
}
在函数functionA
的then
中使用一个数组将valueA
和Promise
一起返回,这样能有效的扁平化圣诞树(回调地狱)。
const converge = (...promises) => (...args) => {
let [head, ...tail] = promises
if (tail.length) {
return head(...args)
.then((value) => converge(...tail)(...args.concat([value])))
} else {
return head(...args)
}
}
functionA(2)
.then((valueA) => converge(functionB, functionC)(valueA))
这样是可行的,写一个帮助函数来屏蔽上下文变量声明。但是这样的代码非常不利于阅读,对于不熟悉这些魔法的人就更难了。
async/await
我们的问题神奇般的消失async function executeAsyncTask () {
const valueA = await functionA()
const valueB = await functionB(valueA)
return function3(valueA, valueB)
}
async/await
处理多个平行请求和上面一个差不多,如果你想一次执行多个异步任务,然后在不同的地方使用它们的值可以使用async/await
轻松搞定。
async function executeParallelAsyncTasks () {
const [ valueA, valueB, valueC ] = await Promise.all([ functionA(), functionB(), functionC() ])
doSomethingWith(valueA)
doSomethingElseWith(valueB)
doAnotherThingWith(valueC)
}
你可以在map
、filter
、reduce
方法中使用async函数,虽然它们看起来不是很直观,但是你可以在控制台中实验以下代码。
function asyncThing (value) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(value), 100)
})
}
async function main () {
return [1,2,3,4].map(async (value) => {
const v = await asyncThing(value)
return v * 2
})
}
main()
.then(v => console.log(v))
.catch(err => console.error(err))
function asyncThing (value) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(value), 100)
})
}
async function main () {
return [1,2,3,4].filter(async (value) => {
const v = await asyncThing(value)
return v % 2 === 0
})
}
main()
.then(v => console.log(v))
.catch(err => console.error(err))
function asyncThing (value) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(value), 100)
})
}
async function main () {
return [1,2,3,4].reduce(async (acc, value) => {
return await acc + await asyncThing(value)
}, Promise.resolve(0))
}
main()
.then(v => console.log(v))
.catch(err => console.error(err))
[ Promise { <pending> }, Promise { <pending> }, Promise { <pending> }, Promise { <pending> } ]
[ 1, 2, 3, 4 ]
10
如果是map迭代数据你会看到返回值为[ 2, 4, 6, 8 ]
,唯一的问题是每个值被AsyncFunction
函数包裹在了一个Promise
中
所以如果想要获得它们的值,需要将数组传递给Promise.All()
来解开Promise
的包裹。
main()
.then(v => Promise.all(v))
.then(v => console.log(v))
.catch(err => console.error(err))
一开始你会等待Promise
解决,然后使用map遍历每个值
function main () {
return Promise.all([1,2,3,4].map((value) => asyncThing(value)))
}
main()
.then(values => values.map((value) => value * 2))
.then(v => console.log(v))
.catch(err => console.error(err))
这样好像更简单一些?
如果在你的迭代器中如果你有一个长时间运行的同步逻辑和另一个长时间运行的异步任务,async/await版本任然常有用
这种方式当你能拿到第一个值,就可以开始做一些计算,而不必等到所有Promise
完成才运行你的计算。尽管结果包裹在Promise
中,但是如果按顺序执行结果会更快。
filter
的问题你可能发觉了,即使上面filter函数里面返回了[ false, true, false, true ]
,await asyncThing(value)
会返回一个promise
那么你肯定会得到一个原始的值。你可以在return之前等待所有异步完成,在进行过滤。
Reducing很简单,有一点需要注意的就是需要将初始值包裹在Promise.resolve
中
Async
函数默认返回一个Promise
,所以你可以使用Promises
来重写任何基于callback
的函数,然后await
等待他们执行完毕。在node中也可以使用util.promisify
函数将基于回调的函数转换为基于Promise
的函数
要转换很简单,.then
将Promise执行流串了起来。现在你可以直接使用`async/await。
function asyncTask () {
return functionA()
.then((valueA) => functionB(valueA))
.then((valueB) => functionC(valueB))
.then((valueC) => functionD(valueC))
.catch((err) => logger.error(err))
}
转换后
async function asyncTask () {
try {
const valueA = await functionA()
const valueB = await functionB(valueA)
const valueC = await functionC(valueB)
return await functionD(valueC)
} catch (err) {
logger.error(err)
}
}
Rewriting Nod
使用Async/Await
将很大程度上的使应用程序具有高可读性,降低应用程序的处理复杂度(如:错误捕获),如果你也使用 node v8+的版本不妨尝试一下,或许会有新的收获。
如有错误麻烦留言告诉我进行改正,谢谢阅读
branch
方法接收三个函数参数,第一个函数返回一个布尔值如果为true则调用branch
第二个函数,如果为false则调用branch
第三个函数。第二个和第三个为高阶函数,在函数里面可以为组件增强功能。
branch(
test: (props: Object) => boolean,
left: HigherOrderComponent,
right: ?HigherOrderComponent
): HigherOrderComponent
const { compose, branch, withProps } = Recompose;
const Foo = compose(
branch(
props => props.isShow,
withProps({ title: '我被显示了' }),
withProps({ title: '你看不到我' }),
),
)(({ title }) => (
<div>{ title }</div>
))
在codepen在线预览
withReducer
类似于withState()
,但是它的更新模式使用了redux
相通的方法。withReducer
接收4个参数,第一个参数为state 名称
,第二个参数为dispatch 方法名
,第三个参数为state 更新函数
,第四个参数为初始化状态。withReducer
和withState
是同样的操作,他们的数据都是保存在代理组件的state
中。再向外暴露一个更新的方法。
withReducer<S, A>(
stateName: string,
dispatchName: string,
reducer: (state: S, action: A) => S,
initialState: S | (ownerProps: Object) => S
): HigherOrderComponent
const { compose, withReducer } = Recompose;
const Foo = compose(
withReducer('state', 'dispatch', (state, action) => {
if(action.type === 'SETCOUNTER') {
return { ...state, counter: action.value };
}
return state;
}, { counter: 0 }),
)(({ state: { counter }, dispatch }) => (
<div>
<p>Counter: {counter}</p>
<button onClick={() => dispatch({ type: 'SETCOUNTER', value: 10 })}>设置Counter</button>
</div>
))
在codepen在线预览
withPropsOnChange
接收两个参数,第一个参数指定了那些props
被更改后需要重新调用计算函数,可以是一个数组里面包含了指定props
的key,也可以包含一个自定义函数做一些逻辑后返回一个布尔值。第二个参数也是一个函数参数是props
,返回一个对象它是组件的props
,其他props也会传递下来,类似于withProps
。
withPropsOnChange(
shouldMapOrKeys: Array<string> | (props: Object, nextProps: Object) => boolean,
createProps: (ownerProps: Object) => Object
): HigherOrderComponent
const TriangleArea = withPropsOnChange(
['a', 'h'],
(props) => ({
area: props.a * props.h / 2,
})
)(({ area, title = '' }) => (
<p>{title}{area}</p>
))
只有当TriangleArea
组件中props.a
或props.h
被改变时才会计算area,这样有助于提高性能
在codepen在线预览
onlyUpdateForPropTypes
和onlyUpdateForKeys
相似哈,但是它不通过指定props
来判断更新,它是根据组件的propTypes
来判断更新,当一个组件没有指定任何propTypes
时组件不会有任何更新。
onlyUpdateForPropTypes: HigherOrderComponent
const { compose, pure, setPropTypes, onlyUpdateForPropTypes } = Recompose;
const Foo = compose(
pure,
onlyUpdateForPropTypes,
setPropTypes({
title: PropTypes.string.isRequired,
}),
)(({ title }) => (
<div>{console.log('render')}{title}</div>
))
在codepen在线预览
lifecycle
是React Component API的高阶函数版本,通过它可以覆盖React原来的方法除了render
函数外。
lifecycle(
spec: Object,
): HigherOrderComponent
const { compose, pure, lifecycle } = Recompose;
const Foo = compose(
pure,
lifecycle({
componentDidMount() {
alert('组件已被加载');
}
}),
)(() => (
<div>Hello,World!</div>
));
在codepen在线预览
先说结论useCallback
和useMemo
都可缓存函数的引用或值,但是从更细的使用角度来说useCallback
缓存函数的引用,useMemo
缓存计算数据的值。
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
根据官网文档的介绍我们可理解:在a
和b
的变量值不变的情况下,memoizedCallback
的引用不变。即:useCallback
的第一个入参函数会被缓存,从而达到渲染性能优化的目的。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
根据官方文档的介绍我们可理解:在a
和b
的变量值不变的情况下,memoizedValue
的值不变。即:useMemo
函数的第一个入参函数不会被执行,从而达到节省计算量的目的。
我做了这样一个简单示例,我们来分析一下现象。
// 在Hooks中获取上一次指定的props
const usePrevProps = value => {
const ref = React.useRef();
React.useEffect(() => {
ref.current = value;
});
return ref.current;
}
function App() {
const [count, setCount] = React.useState(0);
const [total, setTotal] = React.useState(0);
const handleCount = () => setCount(count + 1);
const handleTotal = () => setTotal(total + 1);
const prevHandleCount = usePrevProps(handleCount);
console.log('两次处理函数是否相等:', prevHandleCount === handleCount);
return (
<div>
<div>Count is {count}</div>
<div>Total is {total}</div>
<br/>
<div>
<button onClick={handleCount}>Increment Count</button>
<button onClick={handleTotal}>Increment Total</button>
</div>
</div>
)
}
ReactDOM.render(<App />, document.body)
我们重点看这一行
const handleCount = () => setCount(count + 1);
根据我们之前的理解,我们知道每次App组件渲染时这个handleCount
都是重新创建的一个新函数。
const prevHandleCount = usePrevProps(handleCount);
console.log('两次处理函数是否相等:', prevHandleCount === handleCount);
我们也可以通过比较上一次的prevHandleCount
和本次的handleCount
。可以明确的知道每次渲染时handleCount
都是重新创建的一个新函数。
问题:它有什么问题呢?当我们将handleCount
作为props传递给其他组件时会导致像PureComponent
、shouldComponentUpdate
、React.memo
等相关优化失效(因为每次都是不同的函数)
展示问题Gif:
为了解决上述的问题,我们需要引入useCallback
,通过使用它的依赖缓存功能,在合适的时候将handleCount
缓存起来。我创建了一个简单示例,来看看是如何解决的吧。
// 在Hooks中获取上一次指定的props
const usePrevProps = value => {
const ref = React.useRef();
React.useEffect(() => {
ref.current = value;
});
return ref.current;
}
function App() {
const [count, setCount] = React.useState(0);
const [total, setTotal] = React.useState(0);
const handleCount = React.useCallback(() => setCount(count => count + 1), []);
const handleTotal = () => setTotal(total + 1);
const prevHandleCount = usePrevProps(handleCount);
console.log('两次处理函数是否相等:', prevHandleCount === handleCount);
return (
<div>
<div>Count is {count}</div>
<div>Total is {total}</div>
<br/>
<div>
<button onClick={handleCount}>Increment Count</button>
<button onClick={handleTotal}>Increment Total</button>
</div>
<AotherComponent onClick={handleCount} />
</div>
)
}
const AotherComponent = React.memo(function AotherComponent({ onClick }) {
console.log('AotherComponent 组件渲染');
return (
<button onClick={onClick}>AotherComponent - Inrement Count</button>
)
})
ReactDOM.render(<App />, document.body)
这次我们重点看这行
const handleCount = React.useCallback(() => setCount(count => count + 1), []);
我使用useCallback
来缓存了函数,依赖项(deps)是一个空数组它代表这个函数在组件的生成周期内会永久缓存
。
const AotherComponent = React.memo(function AotherComponent({ onClick }) {
console.log('AotherComponent 组件渲染');
return (
<button onClick={onClick}>AotherComponent - Inrement Count</button>
)
})
因为我们的handleCount
是一个缓存函数,所以当我们传递给经过React.memo
优化的组件AotherComponent
时不会触发渲染
温馨提示:在选择useCallback
的依赖(deps)时请经过仔细的考虑,比如下面这样的依赖是达不到最好的优化效果,因为当我们增加了一次count
时,handleCount
的引用就会更改。
解决问题Gif,我们可以看到prevHandleCount等于handleCount,也没有多余的渲染:
useMemo
和useCallback
几乎是99%像是,当我们理解了useCallback后理解useMemo就非常简单。
他们的唯一区别就是:useCallback
是根据依赖(deps)缓存第一个入参的(callback)。useMemo
是根据依赖(deps)缓存第一个入参(callback)执行后的值。
你明白了吗? 如果还没明白我贴一下useCallback
和useMemo
的源码你来看看区别。
// 注:为了方便理解我省去了一些flow语法
function updateCallback(callback, deps) {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
}
hook.memoizedState = [callback, nextDeps];
return callback;
}
function updateMemo(nextCreate, deps) {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
}
const nextValue = nextCreate(); // 🤩
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
聪明的你一定看出来区别是啥了对吧。
useMemo
一般用于密集型计算大的一些缓存。
下面我写了一个简单示例,来展示useMemo
如何使用的。
// 在Hooks中获取上一次指定的props
const usePrevProps = value => {
const ref = React.useRef();
React.useEffect(() => {
ref.current = value;
});
return ref.current;
}
function App() {
const [count, setCount] = React.useState(0);
const [total, setTotal] = React.useState(0);
const calcValue = React.useMemo(() => {
return Array(100000).fill('').map(v => /*一些大量计算*/ v);
}, [count]);
const handleCount = () => setCount(count => count + 1);
const handleTotal = () => setTotal(total + 1);
const prevCalcValue = usePrevProps(calcValue);
console.log('两次计算结果是否相等:', prevCalcValue === calcValue);
return (
<div>
<div>Count is {count}</div>
<div>Total is {total}</div>
<br/>
<div>
<button onClick={handleCount}>Increment Count</button>
<button onClick={handleTotal}>Increment Total</button>
</div>
</div>
)
}
ReactDOM.render(<App />, document.body)
这次我们重点看这行,只有当count
变量值改变的时候才会执行useMemo
第一个入参的函数。
const calcValue = React.useMemo(() => {
return Array(100000).fill('').map(v => /*一些大量计算*/ v);
}, [count]);
通过useMemo
的依赖我们就可以只在指定变量值更改时才执行计算,从而达到节约内存消耗。
我们一起回顾了useCallback
和useMemo
的基本使用方法,接着找到了影响性能的根本原因然后通过useCallback
如何去解决性能问题。最后我们学习了如何使用useMemo
去缓存了计算量密集的函数。我们还通过观察React Hooks源码观察了useCallback
和useMemo
最根本的区别,这让我们在开发时可以做出正确的选择。
如果有错误请斧正
感谢阅读
renameProp
renameProps
可以改变指定props
的名字,它们的功能类似,renameProp
接收两个参数第一个参数为老的props
第二个参数为命名后的props
。renameProps
接收一个对象,其key
值为老的props
,value
为命名后的props
。
renameProp(
oldName: string,
newName: string
): HigherOrderComponent
renameProps(
nameMap: { [key: string]: string }
): HigherOrderComponent
const { compose, renameProp, renameProps } = Recompose;
const Foo = compose(
renameProp('title', 'headline'),
renameProps({ desc: 'description', age: 'old' }),
)(({ headline, description, old }) => ([
<div>{headline}</div>,
<div>{description}{old}</div>
]))
在codepen在线预览
容器(container)只是应用了附加配置的普通Linux进程。启动以下的Redis容器,我们可以用来了解幕后的情况。
docker run -d --name=db redis:alpine
Docker容器启动一个名为redis-server的进程。我们可以查看所有正在运行的进程,包括Docker启动的进程。
ps aux | grep redis-server
Docker可以通过以下方式帮助我们识别有关进程的信息,包括进程ID(PID)、父进程ID(PPID)。
docker top db
上面命令输出如下:
$ docker top db
UID PID PPID C STIME TTY TIME CMD
999 1109 1094 0 15:06 ? 00:00:00 redis-server
父进程ID是什么呢?使用ps aux | grep <ppid>
找到父进程ID,有可能是容器(Containerd)。
这里我输入ps aux | grep 1094
,得到如下结果:
root 1094 0.0 0.7 8924 7860 ? Sl 15:06 0:00 docker-containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0 -address /var/run/docker/containerd/docker-containerd.sock -containerd-binary /usr/bin/docker-containerd -runtime-root /var/run/docker/runtime-runc -debug
root 1197 0.0 0.0 14220 928 pts/0 S+ 15:10 0:00 grep --color=auto 1094
pstree
命令将会列出所有子进程,使用pstree -c -p -A $(pgrep dockerd)
查看Docker进程树
输出结果如下:
dockerd(677)-+-docker-containe(718)-+-docker-containe(1094)-+-redis-server(1109)-+-{redis-server}(1139)
| | | |-{redis-server}(1140)
| | | `-{redis-server}(1141)
| | |-{docker-containe}(1095)
| | |-{docker-containe}(1096)
| | |-{docker-containe}(1097)
| | |-{docker-containe}(1098)
| | |-{docker-containe}(1100)
| | `-{docker-containe}(1101)
| |-{docker-containe}(721)
| |-{docker-containe}(722)
| |-{docker-containe}(723)
| |-{docker-containe}(730)
| |-{docker-containe}(738)
| |-{docker-containe}(740)
| |-{docker-containe}(755)
| `-{docker-containe}(757)
|-{dockerd}(704)
|-{dockerd}(705)
|-{dockerd}(706)
|-{dockerd}(717)
|-{dockerd}(719)
|-{dockerd}(737)
|-{dockerd}(739)
|-{dockerd}(741)
|-{dockerd}(748)
`-{dockerd}(1093)
你亲眼所见了吧,从Linux角度来看,这些是标准进程,并且与我们系统上的其他进程具有相同的属性。
Linux只是一系列不可意思的文件和内容,这使得探索了解幕后发生的事情变得很有意思。
每个进程的配置在/proc
目录中定义。如果知道进程ID,则可以配置标识(identify)目录。
下面的命令将列出/proc
的所有内容,并存储Redis PID已供后面使用。
DBPID=$(pgrep redis-server)
echo Redis is $DBPID
Redis is 1109
$ ls /proc
1 1227 131 169 21 25 32 462 55 62 696 768 acpi devices interrupts kmsg misc schedstat sysrq-trigger version_signature
10 124 132 17 217 26 33 465 56 63 7 775 buddyinfo diskstats iomem kpagecgroup modules scsi sysvipc vmallocinfo
1094 125 133 171 22 262 34 494 57 64 708 8 bus dma ioports kpagecount mounts self thread-self vmstat
11 126 134 172 225 267 35 5 58 65 715 85 cgroups driver irq kpageflags mtrr slabinfo timer_list zoneinfo
1109 127 135 18 23 27 36 52 59 66 718 86 cmdline execdomains kallsyms loadavg net softirqs timer_stats
12 129 140 19 232 28 4 53 60 675 72 87 consoles fb kcore locks pagetypeinfo stat tty
1205 13 15 2 233 29 455 54 61 677 720 9 cpuinfo filesystems keys mdstat partitions swaps uptime
1220 130 16 20 24 3 457 543 610 68 725 905 crypto fs key-users meminfo sched_debug sys version
每个进程在不同文件中定义了自己的配置和安全设置 ls /proc/$DBPID
命令查看我们Redis Server的进程配置文件输出如下
attr cgroup comm cwd fd io map_files mountinfo net oom_adj pagemap root sessionid stack status timers
autogroup clear_refs coredump_filter environ fdinfo limits maps mounts ns oom_score personality sched setgroups stat syscall uid_map
auxv cmdline cpuset exe gid_map loginuid mem mountstats numa_maps oom_score_adj projid_map schedstat smaps statm task wchan
举个例子,你可以查看和更新该进程的环境变量 cat /proc/$DBPID/environ
, 输出如下
HOSTNAME=a125f186bfbeSHLVL=2REDIS_DOWNLOAD_SHA=61db74eabf6801f057fd24b590232f2f337d422280fd19486eca03be87d3a82bHOME=/home/redisPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binREDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-5.0.7.tar.gzREDIS_VERSION=5.0.7PWD=/data
docker exec -it db env
通过Docker容器查看环境变量,输出如下
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=a125f186bfbe
TERM=xterm
REDIS_VERSION=5.0.7
REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-5.0.7.tar.gz
REDIS_DOWNLOAD_SHA=61db74eabf6801f057fd24b590232f2f337d422280fd19486eca03be87d3a82b
HOME=/root
容器的基本组成部分之一就是名称空间。名称空间的概念是为了限制哪些进程可以看到和访问系统的某些部分,例如其他网络接口或进程。
启动容器后,容器运行时(例如Docker)将创建新的名称空间以对进程进行沙箱处理。通过在它自己的PID 命名空间(Pid namespace)运行一个进程,它将看起来像是系统上唯一的进程。
可用的命名空间是:
Mount (mnt)
Process ID (pid)
Network (net)
Interprocess Communication (ipc)
UTS (hostnames)
User ID (user)
Control group (cgroup)
在不使用诸如Docker之类的运行时的情况下,进程仍可以在其自己的名称空间中运行。一种帮助工具是unshare。
unshare --help
使用unshare,可以启动进程并创建新的名称空间,例如Pid。通过从主机 unshare Pid命名空间,bash提示似乎是机器上唯一运行的进程。看起来好像bash是计算机上唯一运行的进程。
$ sudo unshare --fork --pid --mount-proc bash
$ ps
PID TTY TIME CMD
1 pts/0 00:00:00 bash
9 pts/0 00:00:00 ps
$ exit
在幕后,命名空间是磁盘上的inode位置。这允许进程共享/重用相同的名称空间,从而允许它们查看和交互。
列出所有命名空间
$ ls -lha /proc/$DBPID/ns/
total 0
dr-x--x--x 2 999 packer 0 Jan 17 15:06 .
dr-xr-xr-x 9 999 packer 0 Jan 17 15:06 ..
lrwxrwxrwx 1 999 packer 0 Jan 17 15:42 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 999 packer 0 Jan 17 15:18 ipc -> ipc:[4026532157]
lrwxrwxrwx 1 999 packer 0 Jan 17 15:18 mnt -> mnt:[4026532155]
lrwxrwxrwx 1 999 packer 0 Jan 17 15:06 net -> net:[4026532160]
lrwxrwxrwx 1 999 packer 0 Jan 17 15:18 pid -> pid:[4026532158]
lrwxrwxrwx 1 999 packer 0 Jan 17 15:18 user -> user:[4026531837]
lrwxrwxrwx 1 999 packer 0 Jan 17 15:18 uts -> uts:[4026532156]
另一个工具·NSEnter·用于将进程附加到现有的命名空间。用于调试目的
$ nsenter --help
$ nsenter --target $DBPID --mount --uts --ipc --net --pid ps aux
PID USER TIME COMMAND
1 redis 0:04 redis-server
15 root 0:00 ps aux
使用Docker,可以使用以下语法共享这些命名空间container:<container-name>
。例如,下面的命令会将nginx连接到数据库命名空间。
$ docker run -d --name=web --net=container:db nginx:alpine
$ WEBPID=$(pgrep nginx | tail -n1)
$ echo nginx is $WEBPID
$ cat /proc/$WEBPID/cgroup
网络共享后,它仍将作为命名空间列出
$ ls -lha /proc/$WEBPID/ns/
total 0
dr-x--x--x 2 systemd-network systemd-journal 0 Jan 17 15:49 .
dr-xr-xr-x 9 systemd-network systemd-journal 0 Jan 17 15:46 ..
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 ipc -> ipc:[4026532225]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 mnt -> mnt:[4026532223]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 net -> net:[4026532160]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 pid -> pid:[4026532226]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 user -> user:[4026531837]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 uts -> uts:[4026532224]
但是,两个进程的net命名空间指向相同的位置。
$ ls -lha /proc/$WEBPID/ns/ | grep net
dr-x--x--x 2 systemd-network systemd-journal 0 Jan 17 15:49 .
dr-xr-xr-x 9 systemd-network systemd-journal 0 Jan 17 15:46 ..
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 ipc -> ipc:[4026532225]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 mnt -> mnt:[4026532223]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 net -> net:[4026532160]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 pid -> pid:[4026532226]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 user -> user:[4026531837]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 uts -> uts:[4026532224]
$ ls -lha /proc/$DBPID/ns/ | grep net
lrwxrwxrwx 1 999 packer 0 Jan 17 15:06 net -> net:[4026532160]
容器进程的重要部分是拥有独立于主机的不同文件的能力。这就是我们可以基于系统上运行的不同操作系统拥有不同的Docker映像的方式。
Chroot允许进程从不同的根目录启动到父操作系统。这允许不同的文件出现在根目录中。
CGroup限制了进程可以消耗的资源量。这些cgroup是在/ proc目录内的特定文件中定义的值。
运行一下命令查看这些映射
$ cat /proc/$DBPID/cgroup
11:pids:/docker/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0
10:perf_event:/docker/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0
9:net_cls,net_prio:/docker/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0
8:hugetlb:/docker/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0
7:blkio:/docker/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0
6:cpuset:/docker/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0
5:freezer:/docker/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0
4:cpu,cpuacct:/docker/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0
3:devices:/docker/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0
2:memory:/docker/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0
1:name=systemd:/docker/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e
这些映射到磁盘上其他cgroup目录,位于:
$ ls /sys/fs/cgroup/
blkio cpu cpuacct cpu,cpuacct cpuset devices freezer hugetlb memory net_cls net_cls,net_prio net_prio perf_event pids systemd
CPU统计信息和使用情况也存储在文件中!
$ cat /sys/fs/cgroup/cpu,cpuacct/docker/$DBID/cpuacct.stat
user 265
system 332
CPU份额限制也在此处定义
$ cat /sys/fs/cgroup/cpu,cpuacct/docker/$DBID/cpu.shares
1024
容器内存配置的所有Docker cgroup都存储在以下位置:
$ ls /sys/fs/cgroup/memory/docker/
085ac378ebb98d1f7d4b4cb233534bacfb057530a6e8f7e42b132faf23c5e804 memory.kmem.limit_in_bytes memory.limit_in_bytes memory.swappiness
a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0 memory.kmem.max_usage_in_bytes memory.max_usage_in_bytes memory.usage_in_bytes
cgroup.clone_children memory.kmem.slabinfo memory.move_charge_at_immigrate memory.use_hierarchy
cgroup.event_control memory.kmem.tcp.failcnt memory.numa_stat notify_on_release
cgroup.procs memory.kmem.tcp.limit_in_bytes memory.oom_control tasks
memory.failcnt memory.kmem.tcp.max_usage_in_bytes memory.pressure_level
memory.force_empty memory.kmem.tcp.usage_in_bytes memory.soft_limit_in_bytes
memory.kmem.failcnt memory.kmem.usage_in_bytes memory.stat
每个目录都根据Docker分配的容器ID进行分组。
$ DBID=$(docker ps --no-trunc | grep 'db' | awk '{print $1}')
$ WEBID=$(docker ps --no-trunc | grep 'nginx' | awk '{print $1}')
$ ls /sys/fs/cgroup/memory/docker/$DBID
cgroup.clone_children memory.kmem.failcnt memory.kmem.tcp.limit_in_bytes memory.max_usage_in_bytes memory.soft_limit_in_bytes notify_on_release
cgroup.event_control memory.kmem.limit_in_bytes memory.kmem.tcp.max_usage_in_bytes memory.move_charge_at_immigrate memory.stat tasks
cgroup.procs memory.kmem.max_usage_in_bytes memory.kmem.tcp.usage_in_bytes memory.numa_stat memory.swappiness
memory.failcnt memory.kmem.slabinfo memory.kmem.usage_in_bytes memory.oom_control memory.usage_in_bytes
memory.force_empty memory.kmem.tcp.failcnt memory.limit_in_bytes memory.pressure_level memory.use_hierarchy
Docker的特性之一是能够控制内存限制。这是通过cgroup设置完成的,默认情况下,容器对内存没有限制。我们可以通过docker stats命令查看。
$ docker stats db --no-stream
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
a125f186bfbe db 0.20% 8.277MiB / 992.1MiB 0.83% 1.3kB / 0B 0B / 0B 4
内存引用存储在一个名为memory.limit_in_bytes
的文件中。通过写入文件,我们可以更改进程的限制。
echo 8000000 > /sys/fs/cgroup/memory/docker/$DBID/memory.limit_in_bytes
如果你再去读它,你会看到已经被转换为了7999488
$ cat /sys/fs/cgroup/memory/docker/$DBID/memory.limit_in_bytes
7999488
再次检查Docker Stats时,该进程的内存限制现在为7.629M
$ docker stats db --no-stream
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
a125f186bfbe db 0.19% 7.52MiB / 7.629MiB 98.57% 1.3kB / 0B 233kB / 0B 4
Linux的所有操作都是通过syscall完成的。内核有330个系统调用,它们执行诸如读取文件,关闭句柄和检查访问权限之类的操作
AppArmor是一个应用程序定义的配置文件,它描述了进程可以访问系统的哪些部分。
可以通过以下方式查看分配给流程的当前AppArmor配置文件
$ cat /proc/$DBPID/attr/current
docker-default (enforce)
Docker的默认AppArmor配置文件是docker-default (enforce)
在Docker 1.13之前,它将AppArmor配置文件存储在/etc/apparmor.d/docker-default(在Docker启动时被覆盖,因此用户无法修改它)。在v1.13之后,Docker现在在tmpfs中生成docker-default,使用apparmor_parser将其加载到内核中,然后删除该文件。
Seccomp提供了限制可以进行哪些系统调用,阻止安装内核模块或更改文件权限等方面的功能。
当分配给一个进程时,这意味着该进程将限于系统调用的一部分。如果它尝试调用被阻止的系统调用,则会收到错误“不允许操作”。
SecComp的状态也在文件中定义
$ cat /proc/$DBPID/status
$ cat /proc/$DBPID/status | grep Seccomp
该flag的含义是:0:禁用 1:严格 2:过滤
Capabilities是有关进程或用户有权执行的操作的分组。这些功能可能涵盖多个系统调用或操作,例如更改系统时间或主机名
状态文件还包含功能标志。一个进程可以丢弃尽可能多的功能以确保其安全性。
$ cat /proc/$DBPID/status | grep ^Cap
这些标志被存储为位掩码,可以用capsh解码
$ capsh --decode=00000000a80425fb
this在javascript中已经相当灵活,把它放到React中给我们的选择就更加困惑了。下面一起来看看React this
的5种绑定方法。
如果你使用的是React 15及以下的版本,你可能使用过React.createClass
函数来创建一个组件。你在里面创建的所有函数的this
将会自动绑定到组件上。
const App = React.createClass({
handleClick() {
console.log('this > ', this); // this 指向App组件本身
},
render() {
return (
<div onClick={this.handleClick}>test</div>
);
}
});
但是需要注意随着React 16版本的发布官方已经将改方法从React中移除
如果你使用React.Component创建一个组件,在其中给某个组件/元素一个onClick属性,它现在并会自定绑定其this
到当前组件,解决这个问题的方法是在事件函数后使用.bing(this)
将this绑定到当前组件中。
class App extends React.Component {
handleClick() {
console.log('this > ', this);
}
render() {
return (
<div onClick={this.handleClick.bind(this)}>test</div>
)
}
}
这种方法很简单,可能是大多数初学开发者在遇到问题后采用的一种方式。然后由于组件每次执行render
将会重新分配函数这将会影响性能。特别是在你做了一些性能优化之后,它会破坏PureComponent性能。不推荐使用
这种方法使用了ES6的上下文绑定来让this
指向当前组件,但是它同第2种存在着相同的性能问题,不推荐使用
class App extends React.Component {
handleClick() {
console.log('this > ', this);
}
render() {
return (
<div onClick={e => this.handleClick(e)}>test</div>
)
}
}
下面的方法可以避免这些麻烦,同时也没有太多额外的麻烦。
为了避免在render
中绑定this
引发可能的性能问题,我们可以在constructor
中预先进行绑定。
class App extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log('this > ', this);
}
render() {
return (
<div onClick={this.handleClick}>test</div>
)
}
}
然后这种方法很明显在可读性和维护性上没有第2种和第3种有优势,但是第2种和第3种由于存在潜在的性能问题不推荐使用,那么现在推荐 ECMA stage-2 所提供的箭头函数绑定。
要使用这个功能,需要在.babelrc种开启stage-2功能,绑定方法如下:
class App extends React.Component {
constructor(props) {
super(props);
}
handleClick = () => {
console.log('this > ', this);
}
render() {
return (
<div onClick={this.handleClick}>test</div>
)
}
}
这种方法有很多优化:
箭头函数会自动绑定到当前组件的作用域种,不会被call
改变
它避免了第2种和第3种的可能潜在的性能问题
它避免了第4种绑定时大量重复的代码
总结:
如果你使用ES6和React 16以上的版本,最佳实践是使用第5种方法来绑定this
参考资料:
感谢阅读
找到代理地址:127.0.0.1:50880
修改~/.bash_profile配置文件入下:
# proxy
export http_proxy=http://127.0.0.1:50880
export https_proxy=$http_proxy
如果不存在 ~/.bash_profile 则新建一个
执行 source 命令让配置文件立刻生效,source ~/.bash_profile
测试是否链接 curl -i https://google.com
以上设置只针对http(s)的协议,如果clone的git仓库地址是git协议开头的,则无法使用代理速度还是非常非常的慢(6k~12k)。
配置代理后的效果:
这个时候需要配置ssh的代理(git协议是特殊的协议它走的是ssh的通道)
具体配置ssh的方法:
编辑 ~/.ssh/config
,没有则创建一个
Host github.com
User git
ProxyCommand /usr/bin/nc -X 5 -x <socks_host>:<socks_port> %h %p
git特殊协议代理方法(我测试无效)
配置git配置(~/.gitconfig)
[core]
gitproxy = /path/to/gitproxy for github.com
或使用命令更新git配置项:git config --global core.gitproxy "/path/to/gitproxy for github.com"
代理脚本:
#!/bin/sh
#
# Proxy wrapper for git protocol (9418).
#
# git config --global core.gitproxy "ido gitproxy"
#
exec /usr/bin/nc -X 5 -x <socks_host>:<socks_port> $1 $2
参考来源:https://yechengfu.com/blog/2015/01/16/use-git-behind-proxy/
我们日常开发中基本上在使用javascript进行事件绑定都是这样子的
elem.addEventListener(type, listener[, useCapture]);
我们可以将绑定函数换成一个对象,就像下面这样
elem.addEventListener(type, this[, useCapture]);
其实在事件绑定中的监听函数也可以使用一个对象,事件触发之后会调用对象中的handleEvent
函数进行事件的统一处理。
下面是一个使用事件绑定使用对象的例子
<div class="wrap">
<button id="js_submit">提交</button>
<textarea id="js_desc"></textarea>
</div>
var UiEvent = function() {}
UiEvent.prototype = {
constructor: UiEvent,
handleEvent: function(event) {
var eventName = 'on' + event.type;
if(this[eventName]) {
this[eventName](event);
}
}
};
var Submit = function(){
var name = 'js_submit';
var elem = document.getElementById(name);
this.bindEvent = function() {
elem.addEventListener('click', this);
}
// 定义onclick处理函数
this.onclick = function(event) {
console.log(event);
}
};
Submit.prototype = Object.create(UiEvent.prototype);
var submit = new Submit();
submit.bindEvent();
// Textarea
var Textarea = function(){
var name = 'js_desc';
var elem = document.getElementById(name);
this.bindEvent = function() {
elem.addEventListener('keyup', this);
}
// 定义onkeyup处理函数
this.onkeyup = function(event) {
console.log(event.key);
}
};
Textarea.prototype = Object.create(UiEvent.prototype);
var textarea = new Textarea();
textarea.bindEvent();
UiEvent
对象是一个专门同于处理事件的对象,其他的类可以继承此类,进行事件绑定
比如我们这里的Submit
对象,它继承了UiEvent
在事件绑定的时候使用elem.addEventListener('click', this);
当事件触发后会先调用UiEvent
中的handleEvent,它会查找并执行Submit
对象中的onclick方法。
shouldUpdate
是React shouldUpdate
的高阶函数包装,用法同https://reactjs.org/docs/react-component.html#shouldcomponentupdate一样。
shouldUpdate(
test: (props: Object, nextProps: Object) => boolean
): HigherOrderComponent
const Foo = compose(
pure,
shouldUpdate((props, nextProps) => props.title !== nextProps.title) ,
)(({ title }) => (
<div>{console.log('render')}{title}</div>
))
在codepen在线预览
在现实生活中继承是很常见的,除非有其他因素影响,一般情况下父母较高他的孩子也是比较高等等例子。我们在css中也可以看到类似的东西(inherit)。
如果你将一个容器元素color
设置为绿色,除非有一些规则覆盖color
这个颜色,容器内所有元素都会是绿色的。某些属性的值通过父元素传递到子元素的机制称为继承。
在本文中,你将会学习到关于继承的不同方面,以及它如何影响不同元素的外观。
css继承大大减少了创建网站所需要的时间和体力。想象一下你需要写多少css来给body
和他的子元素设置color
。这非常浪费时间,也很容易出错并且难以维护。同样,你可以想象如果强制你给每个子元素设置font-size
,font-family
,这肯定是一场噩梦。
来看一下这个演示
这里我给body
元素定义了font-family
,font-size
和line-height
属性,这些值都被嵌套在body
中的元素继承了。它的好处是给布局提供了一致性,而不需要在多个元素上重复设置同样的属性。
在现实生活中,不是所有的属性都是从父母你来继承过来的,css也是如此,并不是每个子元素的css属性都是默认继承的。事实上如果所有的css属性都被继承,它所带来的效果跟没有继承一样,因为你不得不写很多css来覆盖这种行为。
举个例子,如果border
属性默认被继承的话,设置一个颜色有border
会导致其所有子元素同样拥有border
属性。同样的,如果子元素继承了父元素的background-image
属性结果将非常混乱。
defaultProps
可以为组件增强一个默认属性,类似于withProps
,如果组件未传递某个props
则默认props
会传递给组件。
defaultProps(
props: Object
): HigherOrderComponent
const { compose, defaultProps } = Recompose;
const Foo = compose(
defaultProps({
title: "Hello,World"
}),
)(({ title }) => (
<div>{title}</div>
))
当Foo
组件没有传递title
属性时,默认的title
属性值'Hello,World'将会传递给组件的props
在codepen在线预览
withStateHandlers
介绍withStateHandlers
就是withState
和withHandlers
的结合并且对同一个state
会进行多中操作时使用,它可以把state
、state initial
、set state handler
在一个地方进行创建,每个set state handler
都是两个高阶函数包装的,第一个高阶函数接收(state, props)
,第二个高阶函数接收调用函数的参数。set state handler
函数返回一个对象这个对象用于更新state
。
withStateHandlers
Flow TypewithStateHandlers(
initialState: Object | (props: Object) => any,
stateUpdaters: {
[key: string]: (state:Object, props:Object) => (...payload: any[]) => Object
}
)
withStateHandlers
实例const Foo = compose(
withStateHandlers(
({ initialCount = 0 }) => ({
counter: initialCount
}),
{
handleIncrement: ({ counter }) => (value) => ({
counter: counter + value,
}),
handleDecrement: ({ counter }) => (value) => ({
counter: counter - value,
}),
handleReset: () => (initialCount = 0) => ({
counter: initialCount,
}),
}
),
)(({ counter, handleIncrement, handleDecrement, handleReset }) => (
<div>
<p>Counter:{counter}</p>
<button onClick={() => handleIncrement(1)}>增加</button>
<button onClick={() => handleDecrement(1)}>减少</button>
<button onClick={() => handleReset(0)}>重置</button>
</div>
))
在codepen在线预览
在ruby中有defined?
关键字帮助您检测一个变量是否定义
如果变量存在您将获得它的类型
apple = 1
p defined?(apple)
# => "local-variable"
如果变量没有定义您将获得nil
defined?(yoho) # => nil
这个 defined?
有点类似 javascript 的 typeof
运算符,但是如果你想知道对象的类型可以在对象上使用 class
方法
一些有意思的注意点:
defined?
是一个关键字,不是一个方法
defined?
是ruby中少出几个以 ?
结尾,但并不遵循常规的套路返回 true
或 false
这个关键字很有用,但它存在一些问题。为什么?
因为其运算符优先级很低。
如果您像下面这样做:
defined? apple && apple.size
上面代码返回的结果是这个表达式的结果
因为 apple && apple.size
被解释为了 defined?
的参数
正确的做法应该是:
defined?(apple) && apple.size
ruby还有其他方法检查变量是否定义
local_variables.include?(:apple)
instance_variable_defined?("@food")
但您可能不会使用这些
在99%的情况下,如果没有申明本地变量您会得到一个拼写错误
未定义的实例变量总是为 nil
,所以你需要检查它
可以尝试一下"安全导航操作符"(Ruby 2.3+) ,如果变量不是 nil
会调用这个方法
下面是一个例子:
if @user&.country == "Spain"
# ...
end
上面的代码相当于:
if @user && @user.country == "Spain"
# ...
end
"安全导航操作符"不像 defined?
那样普遍,但是它更容易预测更不容易出错
您可以使用 defined?
检查方法是否定义。但它不是最佳实践
举个例子:
defined?(puts)
# "method"
因为 defined?
是一个关键字而不是方法,所以不能与对象一起使用
我是这个意思:
[].defined?(:size)
# undefined method `defined?' for []:Array
如何达到和类一起使用呢,像下面这样:
[].respond_to?(:size)
# true
[].respond_to?(:orange)
# false
举个例子:
defined?(Object)
# "constant"
defined?(A)
# nil
上面代码更好的方式是使用 const_defined?
方法。代码如下:
Object.const_defined?(:String)
# true
Object.const_defined?(:A)
# false
本章完啦。感谢阅读🙏
在这里统一记录收集开发中常用和非常有帮助的网址,以备以后查阅
路由相关的类和模块在命名空间 ActionDispatch::Routing::Mapper中, 这里我给一个它的链接方便在学习时查看
匹配一个URL模式到一个或多个路由
基础使用
# 匹配格式为 :controller/:action. 请注意这样使用路由不能带参数
match 'account/setting', via: :get
Prefix | Verb | URI Pattern | Controller#Action |
---|---|---|---|
account_setting | GET | /account/setting(.:format) | account#setting |
带参数的路由
# 带参数的路由必须指定 :to 参数, 他才能正确匹配到指定的 controller 和 action
match 'account/setting/:id', to: "account#setting", via: :get
Prefix | Verb | URI Pattern | Controller#Action |
---|---|---|---|
GET | /account/setting/:id(.:format) | account#setting |
在路由匹配中使用通配符并且映射到params
中
match 'songs/*category/:title', to: 'songs#show', via: :get
Prefix | Verb | URI Pattern | Controller#Action |
---|---|---|---|
GET | /songs/*category/:title(.:format) | songs#show |
上面的通配符路由会将songs/rock/classic/stairway-to-heaven
路径做匹配和映射
<ActionController::Parameters {"controller"=>"songs", "action"=>"show", "category"=>"rock/classic/abc", "title"=>"stairway-to-heaven"} permitted: false>
路由映射到 controller 和 action的几种写法
# 短写形式, match的第一个参数传递一个hash用来映射对应的路由和控制器
match 'photos/:id' => 'photos#show', via: :get
# 我们上面一直使用的正常模式(比较直观)
match 'photos/:id', to: 'photos#show', via: :get
# 冗余模式, 所有的参数都通过options来指定
match 'photos/:id', controller: 'photos', action: 'show', via: :get
除了以上match
的几种使用方法外, 还可以指定兼容Rack
方式的路由
match 'photos/:id', to: -> (hash) { [200, {}, ['Coming soon']] }, via: :get
路由对应控制器名称
路由对应控制器里的方法名称
可以用于覆盖资源默认的:id
参数, 一但覆盖你可以在控制的方法中使用params[:param]
去访问
resources :photos, param: :title
Prefix | Verb | URI Pattern | Controller#Action |
---|---|---|---|
photos | GET | /photos(.:format) | photos#index |
POST | /photos(.:format) | photos#create | |
new_photo | GET | /photos/new(.:format) | photos#new |
edit_photo | GET | /photos/:title/edit(.:format) | photos#edit |
photo | GET | /photos/:title(.:format) | photos#show |
PATCH | /photos/:title(.:format) | photos#update | |
PUT | /photos/:title(.:format) | photos#update | |
DELETE | /photos/:title(.:format) | photos#destroy |
嗯我们看到之前默认:id
已经被替换为了:title
为了更好配合上面路由的使用方法, 你还可以覆盖对应模型的to_param
方法, 去构造符合路由的url
class Photo < ActiveRecord::Base
def to_param
name
end
end
photo = Photo.find_by(name: 'ruby-china')
photo_path(photo) # => "/photos/ruby-china"
路由的前缀
控制器的命名空间, 这里暂时还没懂. 稍后补上
设置生成路由辅助函数的名称
match "account/setting", via: :get
match "account_as/setting_as", as: 'setting', via: :get
Prefix | Verb | URIPattern | Controller#Action |
---|---|---|---|
account_setting | GET | /account/setting(.:format) | account#setting |
setting | GET | /account_as/setting_as(.:format) | account_as#setting_as |
通过上面的输出可以看出, 我们将account_as/setting_as
路由的生成函数定义为了setting
, 在代码中就可以这样使用setting_url
setting_path
定义路由所匹配的动作(verbs), 相关动作资料可以看这里
match 'path', to: 'c#a', via: :get # get
match 'path', to: 'c#a', via: [:get, :post] # get/post
match 'path', to: 'c#a', via: :all # all http verb
可以为resource(s)定义的资源增加:member
, :collection
, :new
路由. 请注意这个必须在resource(s)
的do语句块中使用
resources :photos do
match 'preview', to: 'photos#preview', on: :new, via: :get
match 'preview', to: 'photos#preview', on: :member, via: :get
match 'preview', to: 'photos#preview', on: :collection, via: :get
end
也等同于如下书写方法
resources :photos do
member do
match 'preview', to: 'photos#preview', via: :get
end
end
Prefix | Verb | URIPattern | Controller#Action |
---|---|---|---|
preview_new_photo | GET | /photos/new/preview(.:format) | photos#preview |
preview_photo | GET | /photos/:id/preview(.:format) | photos#preview |
preview_photos | GET | /photos/preview(.:format) | photos#preview |
可以使用正则表达式或者一个可以响应matches?
方法的对象来对参数进行约束
match 'account/setting/:id', constraints: {id: /[A-Z]\d{5}/}, to: 'account#setting', via: :get # 约束 /account/setting/A12345
match 'account/setting', constraints: { format: 'json' }, to: 'account#setting', via: :get # 这个不是太懂!
class Whitelist
def matches?(request) request.remote_ip == '1.2.3.4' end
end
match 'account/setting', to: 'account#setting', constraints: Whitelist.new, via: :get
Prefix | Verb | URIPattern | Controller#Action |
---|---|---|---|
GET | /account/setting/:id(.:format) | account#setting {:id=>/[A-Z]\d{5}/} | |
account_setting | GET | /account/setting(.:format) | account#setting {:format=>"json"} |
preview_photos | GET | /account/setting(.:format) | account#setting |
设置默认params
的值
match 'account/setting', to: 'account#setting', defaults: { id: 'hello' }, via: :get
params输出:<ActionController::Parameters {"id"=>"hello", "controller"=>"account", "action"=>"setting"} permitted: false>
总结:以上我们学会了match
方法的基础使用和它的一些选项,在往后的使用中更能得心应手。关于get
, post
, delete
, put
其实也是调用的match
方法
感谢阅读
flattenProp
可以将props
中的对象扁平化到props
中,类似于结构{...props.obj}
。
flattenProp(
propName: string
): HigherOrderComponent
const { compose, flattenProp } = Recompose;
const Foo = compose(
flattenProp('obj'),
)(({ title, desc }) => (
<div>{title}, {desc}</div>
));
在codepen在线预览
onlyUpdateForKeys
接收一个数组字符串,只有当指定的props
被修改时才更新。这个api不像shouldUpdate
不用自己写比较逻辑,它的内部会自动使用shouldUpdate
和shallowEqual
进行包装。
onlyUpdateForKeys(
propKeys: Array<string>
): HigherOrderComponent
const { compose, pure, onlyUpdateForKeys } = Recompose;
// 只有当 title props 被更改时才会更新组件(render)
const Foo = compose(
pure,
onlyUpdateForKeys(['title']) ,
)(({ title }) => (
<div>{console.log('render')}{title}</div>
))
在codepen在线预览
在React Conf 2018会议中,Dan Abramov 介绍了 React Hooks。官方的描述为
Hook是一项新功能提案,可让您在不编写类的情况下使用状态和其他React功能。 它们目前处于React v16.7.0-alpha中。计划将在 2019 Q1 推出到主版本中。
以下是React Hooks功能的动机,它解决了现有React中的一些问题
React没有一种将可重用的行为附加到组件的方法(例如链接到store)。如果您使用过一段时间,您可能会使用render props
和height-order components
组件解决这个问题。但是这些模式要求您再使用他们时重构组件,这会很麻烦。如果您使用React DevTools
看一下您的程序,您会发现您的组件被各种组件所包裹这叫做包装地域,比如:providers、comsumers、higher-order components、render props等。这里有一个更深层的根本问题:React需要一个更好的方法来共享状态逻辑。
这就是Hooks
,您可以从组件中提取有状态逻辑,以便可以独立测试和重用。Hooks
允许您不更改组件层次结构的情况下重用有状态逻辑。这样就可以轻松在多组件之间或与社区共享Hooks
。
我们经常不得不维护一些组件,这些组件一开始很简单,随着时间的延伸组件发展成一堆无法管理的有状态逻辑和一些副作用。每个生命周期方法经常包含不相关的逻辑组合。举个例子,组件可能会在componentDidMount
和componentDidUpdate
中拉取一些数据。还有componentDidMount
方法可能还包含一些事件监听的不相关逻辑,并且再componentWillUnmount`中卸载监听。但是完全不相关的代码会合并到一个方法中。是很容易引起bug和不一致性。
在很多情况下,不能将这些组件拆分成更小的组件因为逻辑遍布许多地方。对它们进行测试也很困难。这正是很多人将React和状态管理库结合使用的原因。但是这更容易创建更多的抽象,要求您在许多不同的文件之间跳转,重用组件将变得更加困难。
为了解决这个问题,Hooks
允许您根据相关的功能将他们拆分为一个更小的函数。而不是强制基于声明周期函数进行拆分。您还可以选择使用reducer管理组件的本地状态,使其更具可预测性。
除了使代码重用和代码组织更加困难外,我们发现类(classes
)可能成为学习React的一大障碍。您必须了解它在JavaScript中是如何工作的,这与它在大多数语言中的工作方式有很大不同。您必须明白如何正确的绑定事件处理和还没稳定的新语法,代码非常冗长。大家可能很容易就会明白属性(props)、状态(state)、从上往下的数据流(top-down data flow)但类(classes)就很难理解。React中的函数和类组件之间的区别以及何时使用每个组件导致即使在经验丰富的React开发人员之间也存在分歧。使用函数可以使用prepack更好的优化代码。但是使用类组件不能得到更好的优化。
为了解决这些问题,Hooks 允许您在没有类的情况下使用更多的React功能。
useState
可以让您的函数组件也具备类组件的state功能
使用语法如下:
const [state, setState] = useState(initialState);
useState
返回一个数组,一个是state的值,第二个是更新state的函数
在真实的程序中我们可以这样使用:
function TestUseState() {
const [count, setCount] = React.useState(0);
return (
<div>
<p>useState api</p>
<p>Count: {count} <button onClick={() => setCount(count + 1) }>自增</button></p>
</div>
)
}
使用 useState
需要注意一个事项,当你初始化是一个对象时。使用 setCount
时它不像类组件的 this.setState
会自动合并到 state 中。setCount
会使用当前的值覆盖之前的 state。如下所示
function TestUseStateObject() {
const [state, setState] = React.useState({
count: 0,
greeting: "Hello, World!",
});
const handleAdd = () => {
setState({
count: state.count + 1
})
}
console.log('state > ', state)
return (
<div>
<p>useStateObject api</p>
<p>Count: {state.count} <button onClick={handleAdd}>自增</button></p>
</div>
)
}
我们可以看到,当点击按钮时 state 被替换成了 {count: 1}。如果想要在 state 中使用一个对象需要在更新值的时候把之前的值解构出来,如下所示:
setState({
...state,
count: state.count + 1
})
在函数中使用多个 state
function TestMultipleUseState() {
const [count, setCount] = React.useState(0);
const [name, setName] = React.useState('john');
return (
<div>
<p>useState api</p>
<p>Count: {count} - Name: {name}</p>
</div>
)
}
如需要在线测试请前往codepen useState
默认情况下 useEffect
在完成渲染后运行,我们可以在这里获取DOM和处理其他副作用。但它还有两种不同的运行阶段稍候我会解释。
function TestUseEffect() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log(`组件被更新,Count: ${count}`);
});
return (
<div>
<p>useEffect api</p>
<p>Count: {count} <button onClick={() => setCount(count + 1) }>自增</button></p>
</div>
)
}
上面的 useEffect
在每次组件渲染后运行,每当我们点击自增按钮都会执行一次。
但是如果上面的代码在每次渲染后都执行,如果我们在 useEffect
从服务器拉取数据。造成的结果就是每次渲染后都会从服务器拉取数据。或者是只有某些 props
被更新后才想执行 useEffect
。那么默认的 useEffect
就不是我们想要执行方式,这时 useEffect
提供了第二个参数。
useEffect(didUpdate, [])
useEffect
第二个参数为一个数组。当我们提供第二个参数时,只有第二个参数被更改 useEffect
才会执行。利用第二个参数我们可以模拟出类组件的 componentDidMount
生命周期函数
function TestUseEffectListener() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log('componentDidMount fetch Data...');
}, []);
return (
<div>
<p>TestUseEffectListener</p>
<p>Count: {count} <button onClick={() => setCount(count + 1) }>自增</button></p>
</div>
)
}
上面的代码中 useEffect
只会执行一次,当您点击自增 useEffect
也不会再次执行。
在 useEffect
第一个参数的函数中我们可以返回一个函数用于执行清理功能,它会在ui组件被清理之前执行,结合上面所学的知识使用 useEffect
模拟 componentWillUnmount
生命周期函数
function TestUseEffectUnMount() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
return () => {
console.log('componentUnmount cleanup...');
}
}, []);
return (
<div>
<p>TestUseEffectUnMount</p>
</div>
)
}
上面的代码中,当组件 TestUseEffectUnMount
将要销毁时会,会执行 console.log('componentUnmount cleanup...')
代码
如需要在线测试请前往codepen useEffect
useContext
可以让您在函数中使用 context
,它有效的解决了以前 Provider
、Consumer
需要额外包装组件的问题
使用语法如下:
const context = useContext(Context);
现在让我们来看看实际应用中这个 useContext
是如何使用的,代码如下:
function TestFuncContext() {
const context = React.useContext(ThemeContext);
return (
<div style={context}>TestFuncContext</div>
)
}
我们可以看到上面直接使用 React.useContext(ThemeContext)
就可以获得 context
,而在之前的版本中需要像这样才能获取 <Consumer>({vlaue} => {})</Consumer>
,这极大的简化了代码的书写。
// 之前Consumer的访问方式
function TestNativeContext() {
return (
<ThemeContext.Consumer>
{(value) => {
return (
<div style={value}>TestNativeContext</div>
)
}}
</ThemeContext.Consumer>
);
}
如需要在线测试请前往codepen useContext
useReducer
是 useState
的代提方案。当你有一些更负责的数据时可以使用它。
使用语法如下:
const [state, dispatch] = useReducer(reducer, initialState)
第一个参数是一个 reduce 用来处理到来的 action,函数申明为:(state, action) => ()
。第二个参数是一个初始化的state常量。
在返回值 [state, dispatch]
中,state 就是你的数据。dispatch 可以发起一个 action 到 reducer 中处理。
这个功能给我的感觉就是组件本地的redux,感觉还是不错。在设计一些复杂的数据结构是可以使用
现在让我们来看看实际应用中这个 useReducer
是如何使用的,代码如下:
function TestUseReducer() {
const [state, setState] = React.useReducer((state, action) => {
switch(action.type) {
case 'update':
return {name: action.payload}
default:
return state;
}
}, {name: ''});
const handleNameChange = (e) => {
setState({type: 'update', payload: e.target.value})
}
return (
<div>
<p>你好:{state.name}</p>
<input onChange={handleNameChange} />
</div>
)
}
当改变 input 中的值时会同时更新 state 中的数据,然后显示在界面上
如需要在线测试请前往codepen useReducer
useCallback
和 useMemo
有些相似。它接收一个内联函数和一个数组,它返回的是一个记忆化版本的函数。
使用语法如下:
const memoizedValue = useMemo(() => computeExpensiveValue(a), [a])
useCallback
的第一个参数是一个函数用来执行一些操作和计算。第二个参数是一个数组,当这个数组里面的值改变时 useMemo
会重新执行更新这个匿名函数里面引用到 a
的值。这样描述可能有点不太好理解,下面看一个例子:
function TestUseCallback({ num }) {
const memoizedCallback = React.useCallback(
() => {
// 一些计算
return num;
},
[],
);
console.log('记忆 num > ', memoizedCallback())
console.log('原始 num > ', num);
return (
<div>
<p>TestUseCallback</p>
</div>
)
}
如果我们想监听 num
值的更新重新做一些操作和计算,我们可以给第二个参数放入 num
值,像下面这样:
function TestUseCallback({ num }) {
const memoizedCallback = React.useCallback(
() => {
// 一些计算
return num;
},
[num],
);
console.log('记忆 num > ', memoizedCallback())
console.log('原始 num > ', num);
return (
<div>
<p>TestUseCallback</p>
</div>
)
}
如需要在线测试请前往codepen useCallback
我觉得 useRef
的功能有点像类属性,或者说您想要在组件中记录一些值,并且这些值在稍后可以更改。
使用语法如下:
const refContainer = useRef(initialValue)
useRef
返回一个可变的对象,对象的 current
属性被初始化为传递的参数(initialValue)。返回的对象将持续整个组件的生命周期。
一个保存input元素,并使其获取焦点程序,代码如下:
function TestUseRef() {
const inputEl = React.useRef(null);
const onButtonClick = () => {
// 点击按钮会设置input获取焦点
inputEl.current.focus(); // 设置useRef返回对象的值
};
return (
<div>
<p>TestUseRef</p>
<div>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>input聚焦</button>
</div>
</div>
)
}
useRef
返回的对象您可以在其他地方设置比如: useEffect、useCallback等
如需要在线测试请前往codepen useRef
感谢阅读 🙏
最后做一个广告,我创建了一个前端周刊每周五发布最新的技术文章和开源项目欢迎订阅
compose是一个组合辅助函数,它可以将多个高阶函数合并成一个函数。比如你有一些高阶函数它们需要这样组合执行a(b('input value'))
如果函数较多这种写法就不太合适,这时我们可以使用compose
将多个函数的执行组合成一个函数。例如:compose(a,c)('input value')
。如果想了解更详细的信息请参考阮一峰老师的文章。
compose(...functions: Array<Function>): Function
compose
接收一个或多个函数参数
const { compose } = Recompose;
function a(a) {
return a + '-a func';
}
function b(b) {
return b + '-b func';
}
var c = compose(a, b);
console.log(c('我是原始值')) // 我是原始值-b func-a func
在codepen在线预览
在开发fragmentwall的时候最开始是在本地进行开发,当我部署到服务器时我需要本地连接到服务器的mongodb数据,中间涉及了远程连接和权限验证设置。经过查阅文档已经完成,在此进行记录方便以后查阅和帮助其他需要的朋友。
首先不要开启权限校验,你先创建一个超级用户。使用这个超级用户再去创建其他的用户和权限。
use admin
切换到admin数据,默认mongodb是没有这个数据的不过没有关系,当我们创建一个用户后它会自动创建。
在admin数据库下使用下面的命令你可以给admin
数据库创建一个超级用户
db.createUser({ user: "admin", pwd: "adminpassword", roles: [{ role: "userAdminAnyDatabase", db: "admin" }] })
执行完命令之后需要验证用户是否创建完毕使用命令 show users
可以查看用户信息,如果有刚才创建的admin用户代表创建成功,然后使用exit
退出控制台。
mongodb数据库配置文件默认在/etc/mongod.conf
中,使用你喜欢的编辑器打开它。sudo vim /etc/mongod.conf
然后将如下的安全设置打开(默认它是使用#注释起来的)
security:
authorization: enabled
默认的mongodb监听地址为bindIp: 127.0.0.1
,它只允许本地连接,现在修改为bindIp: 0.0.0.0
,这样你可以远程进行连接了。
net:
port: 27017
bindIp: 0.0.0.0
使用命令sudo service mongod restart
重启你的mongodb服务
这个时候你可以为不同的数据库创建不同的用户和权限了,首选你需要连接上数据库,然后切换到admin数据库中
use admin
切换到admin数据库中`
接着登录超级用户
db.auth("admin", "adminpassword")
跟着再切换到你要授权的其他数据库,比如我们需要将一个数据库testdatabase
授权给monster这个用户,那么先要切换到testdatabase
use testdatabase
接着创建monster用户并且给予对应权限,命令如下
db.createUser({ user: "monster", pwd: "monsterpassword", roles: [{ role: "dbOwner", db: "testdatabase" }] })
执行完创建用户,所有的工作已经完成,下面来验证一下是否正确使用show users
查看用户信息是否正确。然后使用这个用户登录再次验证。
db.auth("monster", "monsterpassword")
show collections
如果上面一切正确,那么monster就有了访问testdatabase
这个数据的权限了
那么mongodb连接的字符串为
mongodb://youruser:yourpassword@localhost/yourdatabase
希望能帮助到你 :)
pure
利用shouldUpdate
的shallowEqual
来确定组件是否更新,它是一个对象浅比较
pure: HigherOrderComponent
const { compose, pure, shouldUpdate, lifecycle } = Recompose;
const Foo = compose(
pure,
shouldUpdate((props, nextProps) => props.title !== nextProps.title) ,
)(({ title }) => (
<div>{console.log('render')}{title}</div>
))
在codepen在线预览
可以结合recompose shouldUpdate
来做自定义的props
比较
在rails中使用React.js框架相关的配置记录
首先使用rails new
命令创建一个rails的基础项目,完整的命令如下:
rails new railsUseReact --skip-turbolinks --skip-coffee --webpack=react
执行上述命令时稍微要等待一会,安装完毕之后即可继续。
现在有了一个模板项目,我们需要创建一个路由用于测试。执行如下命令创建一个新的路由:
rails g controller Welcome index
接着我们需要开两个控制台, 分别在项目根目录执行如下命令
# 控制台1
rails s
# 控制台2
bin/webpack-dev-server
浏览器访问:http://localhost:3000/welcome/index 可以看到我们创建的页面
接下来我们先来看看模板项目为我们创建的一个javascript目录,它用来存放React.js组件相关的代码:
➜ railsUseReact git:(master) ✗ tree app/javascript
app/javascript
└── packs
├── application.js
└── hello_react.jsx # 这里是一个React组件我们会使用它
然后修改app/views/welcome/index.html.erb
使用javascript_pack_tag
加载我们的hello_react组件,代码如下:
文件:app/views/welcome/index.html.erb
<h1>Welcome#index</h1>
<%= javascript_pack_tag 'hello_react' %>
刷新浏览器我们可以看到如下的输出:
Welcome#index
Hello React!
以上的修改可以查看提交记录
接着我们需要来对app/javascript/packs/hello_react.jsx
组件进行一些改动,hello_react.jsx
只保留入口组件。我们把真正的Hello组件单独存放。
创建如下的目录和文件:
➜ railsUseReact git:(master) ✗ tree app/javascript
app/javascript
├── components
│ └── Hello
│ ├── index.js
│ └── style.css
└── 其他文件
修改Hello组件为如下的内容:
文件:app/javascript/components/Hello/index.js
import React from 'react';
import PropTypes from 'prop-types';
import './style.css';
class Hello extends React.Component {
render() {
return (
<div className="hello">Hello {this.props.name}!</div>
)
}
}
Hello.defaultProps = {
name: 'David'
}
Hello.propTypes = {
name: PropTypes.string
}
export default Hello;
文件: app/javascript/packs/hello_react.jsx
import React from 'react'
import ReactDOM from 'react-dom'
import Hello from '../components/Hello';
document.addEventListener('DOMContentLoaded', () => {
ReactDOM.render(
<Hello name="React" />,
document.body.appendChild(document.createElement('div')),
)
})
再次刷新浏览器可以看到如下内容:
Welcome#index
Hello React!
一切工作正常,接下来就配置热加载
启动webpack热加载
文件:config/webpacker.yml
dev_server:
hmr: true # 这里修改为true即可启动热加载
然后我们需要更新hello_react.jsx
组件里面的内容使热加载是可以重新渲染组件
文件: app/javascript/packs/hello_react.jsx
import React from 'react'
import ReactDOM from 'react-dom'
import Hello from '../components/Hello';
const renderApp = (Component) => {
ReactDOM.render(
<Component name="React" />,
document.body.appendChild(document.createElement('div')),
)
}
document.addEventListener('DOMContentLoaded', () => {
renderApp(Hello);
})
if (process.env.NODE_ENV !== 'production' && module.hot) {
module.hot.accept('../components/Hello', () => {
const NextHello = require('../components/Hello').default;
renderApp(NextHello);
});
}
接着安装react-hot-loader
包来自动进行热替换,命令如下:
./bin/yarn add react-hot-loader
然后修改我们的Hello
组件使用react-hot-loader包装
文件: app/javascript/components/Hello/index.js
import React from 'react';
import PropTypes from 'prop-types';
import { hot } from 'react-hot-loader';
import './style.css';
class Hello extends React.Component {
render() {
return (
<div className="hello">Hello {this.props.name}!123</div>
)
}
}
Hello.defaultProps = {
name: 'David'
}
Hello.propTypes = {
name: PropTypes.string
}
export default hot(module)(Hello);
重启服务,然后再次访问一些正常。你可以尝试修改一下app/javascript/components/Hello/index.js
组件的内容比如如下代码,可以通过Chrome 调试中的 Network查看页面并没有刷新然后组件的数据已经重新加载过了
以上的修改可以查看提交记录
整个配置完毕,感谢收看
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.