my-blog's People
my-blog's Issues
详解跨域CORS
这几天做需求确实被跨域问题折磨的不轻,所以整理了一下过程中所遇到的问题以及解决方法,避免重复踩坑。
前言:
跨域问题是每个前端开发者都避不开的问题,在之前只从书上看过这一部分的知识,但是在平时本地开发中却很少碰到跨域的情况,所以对这一部分知识也是一知半解,这不,最近在实习,最开始搬砖搬的确实很开心,直到遇到了万恶的口语问题,被折磨的确实不轻,来来回回折腾了一个周,为了以后不再在跨域问题上踩坑,所以这次必须弄明白!
1、什么是跨域?
说到跨域就不得不提同源策略,同源策略产生原因:协议,域名,端口号,当涉及到这三者的时候就产生了跨域,简单的说就是a.com 域名下的js无法操作b.com或是c.a.com域名下的对象,更详细的说明从网上找了一张图(侵删):
同源策略的目的:
为了保证用户信息的安全,防止恶意的网站窃取数据。
同源策略限制了哪些行为:
① :Cookie、LocalStorage、和IndexDb无法读取。
② :Dom无法获得。
③ : AJAX请求不能发送。
Cookie:
服务器写入浏览器的信息,常用于处理登录,验证身份等场景。如果你请求了接口进行登录,服务器验证通过后会在响应头加入Set-Cookie字段,然后下次再发请求的时候,浏览器会自动将Cookie添加在请求头中。cooki在跨域访问的时候是默认不携带的,当然也有一些方法能够将我们的cookie在发送请求的时候带上,后续会讲到。
Dom:
1.有一天你刚睡醒,收到一封邮件,说是你的银行账号有风险,赶紧点进www.yinghang.com改密码。你吓尿了,赶紧点进去,还是熟悉的银行登录界面,你果断输入你的账号密码,登录进去看看钱有没有少了。
2.睡眼朦胧的你没看清楚,平时访问的银行网站是www.yinhang.com,而现在访问的是www.yinghang.com,这个钓鱼网站做了什么呢?
// HTML
<iframe name="yinhang" src="www.yinhang.com"></iframe>
// JS
// 由于没有同源策略的限制,钓鱼网站可以直接拿到别的网站的Dom
const iframe = window.frames['yinhang']
const node = iframe.document.getElementById('你输入账号密码的Input')
console.log(`拿到了这个${node},我还拿不到你刚刚输入的账号密码吗`)
由此我们知道,同源策略确实能规避一些危险,不是说有了同源策略就安全,只是说同源策略是一种浏览器最基本的安全机制,毕竟能提高一点攻击的成本。其实没有刺不穿的盾,只是攻击的成本和攻击成功后获得的利益成不成正比。
跨域的解决办法:
跨域的方法有很多,比如jsonp,iframe+form等,因为现在的主流跨域解决方案是——跨域资源共CORS,所以在这就主要介绍这一种情况,对其他跨域方法有兴趣的同学可以自行查找。
介绍:
CORS是一个W3C标准,全称是“跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨源服务器发出请求,从而克服了AJAX只能同源使用的限制。 CORS的使用需要浏览器和服务器同时支持。目前,市面上的浏览器都已支持该功能,但是,IE不能低于IE10。IE8+:IE8/9需要使用XDomainRequest对象来支持CORS。
CORS的整个实现过程都是由浏览器自动完成的,对于开发者来说,CORS通信和AJAX通信区别不大。所以实现CORS的关键在于服务器。
CORS分为两种请求方式:简单请求和非简单请求
简单请求:
· 请求方式: HEAD、POST、HEAD
· http头信息不超出一下字段:Accept、Accept-Language 、 Content-Language、 Last-Event-ID、 Content-Type(限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain)
流程:当浏览器发现这次是简单请求时,会在请求头中增加一个Origin字段,它的作用是表明本次请求来自哪个源(协议、域名、端口号),服务器根据这个值,决定是否同意这次请求(同意的Orgigin值在服务器设置)。如果Origin指定的源不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现在这次响应头中没有Access-Control-Allow-Origin字段,就会抛出一个错误,被回调函数捕获。如果Origin指定的源在许可范围内,服务器返回的响应会多出几个信息字段。
Access-Control-Allow-Origin:
必须字段,它的值要么是请求时Origin的值,要么可以搭配*来使用,但是如果要允许跨域访问携带Cookie,则该字段的值必须为明确的地址,不能含有通配符
Access-Control-Allow-Credentials(这是跨域携带Cookie的关键):若允许则设置为true,不允许则不加该字段即可。
Access-Control-Expose-Headers:
Content-Type
如何携带Cookie
我们应该如何在跨域请求中携带Cookie,除了需要服务器做相应设置,前端发送请求时也需要进行一步操作
fetch:
fetch(url, {
method: 'GET',
headers: myHeaders,
credentials: "include"
})
XMLHttpRequest:
xhr.open('GET', url);
xhr.withCredentials = true;
xhr.send();
这个地方需要注意的是,withCredentials的设置必须在open之后,send之前
同时只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。
非简单请求:
非简单请求:
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。非简单请求会发出一次预检测请求,返回码是204,预检测通过才会真正发出请求,这才返回200。这里通过前端发请求的时候增加一个额外的headers来触发非简单请求。
Js垃圾回收机制
一、Js具有垃圾自动回收机制
原理: 周期性执行,找出那些不在继续使用的变量,然后释放其内存。
二、最常见的垃圾回收方式——标记清除方式
原理: 是当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。
工作流程:
当变量进入环境时,声明标记“进入环境”。
当变量离开环境时,标记为“离开环境”。
垃圾回收器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间。
三、 引用计数方式
原理:跟踪记录每个值被引用的次数。
工作流程:
当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为0的值所占用的内存。
React学习笔记——中间件原理篇
前言
react已经出来很久了,其生态圈之庞大,一锅炖不下!各种react-xx,已让我们不堪重负,github上随便一个demo,引入的模块至少都是五指之数+。看着头疼,嚼之无味……。
在此建议新学者,可以从基础的核心模块学起,前期不要考虑那些数量繁多的马仔小弟,边学边写,个人感觉前期核心要学的流程大致如下:
React ——> React + redux + React-redux ——> React + redux + React-redux + React-router ——> React + redux + React-redux + React-router ;
其它的,看情况学习和了解,我也很菜,以上感悟都是针对初学者,希望可以减少他们在学习过程中接触过多的东西,而影响学习信心和乐趣。
都说名字越长,越让学者害怕,applyMiddleware的名字看起来就挺吓人,那么为什么会出现中间件,它是做什么的?它为什么叫中间件?为什么说可以用来解决异步dispatch?经过一段时间的了解,让我渐渐明白了它的工作原理,现在让我们带问题,怀着简单,轻松的心态走进applyMiddleware大讲堂:
为什么会出现中间件?
我们知道redux的核心,就是控制和管理所有的数据输入输出,因此有了dispatch,由于dispatch是一个很纯的纯函数,就是单纯的派发action来更改数据,其功能简单且固定。
假如现在产品经理A某有个需求,要求记录每次的dispatch记录,我们怎么办呢?最简单直接的办法就是在每一个dispatch的前面加上:
console.log('dispatching', action);
dispatch(action)
假如又来一个产品B说,我需要记录每次数据出错的原因,我们怎么办呢?然后我们又需要在对每一个dispatch做修改
try{
dispatch(action)
}catch(err){
console.error('错误报告: ', err)
}
如果我们的程序中有很多的dispatch,我们就需要添加很多的重复代码,虽然编辑器提供批量替换,但这无疑是产生了很多样板代码。
因为所有的需求都是和dispatch息息相关,所以只要我们把日志放进dispatch函数里,不就好了吗,我们只需要更改dispatch函数,把dispatch进行一层封装。
大概的封装就是下面这样:
let next = store.dispatch
store.dispatch = function dispatchAndLog(action) {
console.log('dispatching', action)
next(action)
}
Redux把这个封装的入口写成了一个函数,就叫applyMiddleware。
由此我们明白了applyMiddleware的功能:改造dispatch函数,产生真假dispatch,而中间件就是运行在假真(dispatchAndLog假和next真)之间的代码。
这里我们要对applyMiddleware进行一个准确的定义,它只是一个用来加工dispatch的工厂,而要加工什么样的dispatch出来,则需要我们传入对应的中间件函数(比如上例中的dispatchAndLog),下面我们构造一个精简版的applyMiddleware:
const applyMiddleware = function(middleware){
let next = store.dispatch;
store.dispatch = middleware(store)(next); // 这里传入store,是因为中间件中有可能会用到getState获取数据,比如打印当前用户等需求
}
applyMiddleware(dispatchAndLog)
中间件的串联融合。
中间件的功能各不相同,它们都要融入到dispatch中,在派发action的时候,按照顺序一个个的执行,这是一个费脑经的事情。
假如现在我们有两个中间件 logger和collectError两个中间件函数,那么大概的执行顺序就是 dispatch——>logger改造——>collectError改造。这里我们能看到后面的中间件需要接收到前面改造后的dispatch,
在前面,我们是直接修改store.dispatch,现在我们换一种写法,让每一个中间件函数,接收一个dispatch,然后返回一个改造后的dispatch,来作为下一个中间件函数的next,以此类推,用ES6的写法大概代码如下:
const logger = store => next => action => {
console.log('dispatching', action)
return next(action)
}
const collectError = store => next => action => {
try {
return next(action)
} catch (err) {
console.error('Error!', err)
}
}
然后,我们改造一下applyMiddleware,来接收一个middlewares数组:
function applyMiddleware(middlewares) {
middlewares = middlewares.slice()
middlewares.reverse()
let dispatch = store.dispatch
middlewares.forEach(middleware =>
dispatch = middleware(store)(dispatch)
)
return Object.assign({}, store, { dispatch })
}
上面的middleware(store)(dispatch) 就相当于是 const logger = store => next => {},这就是构造后的dispatch,继续向下传递。这里middlewares.reverse(),进行数组反转的原因,是最后构造的dispatch,实际上是最先执行的。因为在applyMiddleware串联的时候,每个中间件只是返回一个新的dispatch函数给下一个中间件,实际上这个dispatch并不会执行。只有当我们在程序中通过store.dispatch(action),真正派发的时候,才会执行。而此时的dispatch是最后一个中间件返回的包装函数。然后依次向前递推执行。
我们拿logger和collectError来说明:
构造过程:
let next = store.dispatch;
let dispatch1 = logger(store)(next);
// 这时候的console.log('dispatching', action) 是没有执行的
let dispatch2 = collectError(store)(dispatch1);
// 这时候的console.log('Error!', err) 也是没有执行的
store.dispatch = dispatch2;
执行过程:
store.dispatch(action); //假如我们程序中派发了某个action
//相当于是下面这样
dispatch2(action); //此时执行了 console.log('Error', err)
//由于collectError中间件中的next是接收的logger返回函数即dispatch1,所以在开始执行
dispatch1(action); //此时执行了 console.log('dispatching', action)
// 这个例子不太合理,因为错误报告是先 try 的 next(action),但是正常的流程是如此。
React学习笔记——redux源码篇
随着一个多月来对React框架越来越多的使用,对React也有了一些自己的理解,今天就来梳理一下react当中的redux部分
react:
对于一些小型项目而言,React就完全能够满足需求,数据的传递和管理通过state、props就能够完成,但是随着业务的增加,我们数据的传递过程可能是这样的A --> B --> C --> D, 组件D需要A中的某个数据,A需要D相对应的回调,但是这些数据对于B,C而言是无用,如果这个时候单纯的使用React就需要一层层传递,逻辑更加复杂的同时也降低了代码的可读性,增加项目后期的维护成本。或者是兄弟组件之间共用父组件的一些数据,传递时也需要重复传递数据给每个子组件,写起来很是麻烦,可能除了自己以外的同事接过项目以后都想拿着菜刀去找的,所以这个时候就引入redux就很有必要了。
redux:
我只是想找一个地方存一下我的数据,大家有需要就拿,也都可以修改了就行了,那应该放在哪呢?
有了!放全局不就行了?!放在全局大家是可以读取改动了,但是也造成了一个问题,万一哪天小B在改动自己需要的数据同时改动了小C需要的数据或者覆盖了小D的数据,小B倒是没事,可小C、小D可就苦了,莫名其妙出bug,码在家中躺,锅从天上来,三更半夜爬起来找bug还不是常有的事。那我不用全局了,改成私有吧,这样就不能随便被更改了。哎等等,怎么越说越像闭包呢
· 将状态统一放在一个state中,由store来管理这个state。
· 这个store按照reducer的“shape”(形状)创建。
· reducer的作用是接收到action后,输出一个新的状态,对应地更新store上的状态。
· 根据redux的原则指导,外部改变state的最佳方式是通过调用store的dispatch方法,触发一个action,这个action被对应的reducer处理,完成state更新。
· 可以通过subscribe在store上添加一个监听函数。每当调用dispatch方法时,会执行所有 的监听函数。
createStore:
export default function createStore(reducer, preloadedState, enhancer) {
// 如果第二个参数没有传入初始的state,而是传入了enhancer(applyMiddleware), 那就将第二个参数preloadedState赋值给enhancer
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
// 如果传入了enhancer,但enhancer不是一个函数,报错
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
// 此处意味着,如果createStore的时候传入了enhancer,那么在执行时会将createStore传入enhancer中,执行enhancer, 而enhancer的返回值也是一个函数。后面我们还会一起来看applyMiddleware的源码
return enhancer(createStore)(reducer, preloadedState)
}
// 如果没传入enhancer,就继续下面的逻辑
// reducer是要求为一个函数的,如果不是一个函数,报错
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
.....
.....
// 最后createStore就会返回dispatch,subscribe, getState等几个常用的api
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
};
}
//上面的源码就是封装了一些api,最后暴露给用户使用。
getState: 用来获取store中的state的。因为redux为了防止小B犯错小C、小D背锅现象的情况出现,对于state的获取,是得通过getState的api来获取store内部的state。
function getState() {
// 为了确保用户每次都能拿到最新的state,这个地方需要做一次判断,如果isDispatching=true, 说明新的state正在计算中,就报错,反之,返回现在的state
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
'The reducer has already received the state as an argument. ' +
'Pass it down from the top reducer instead of reading it from the store.'
)
}
return currentState
}
dispatch: 该函数是与getState对应的,getState是读,那dispatch就是写。redux中如果要改动state,只能通过发起一个dispatch,传递一个action给reducer,告诉reducer要改动的数据,reducer会根据action和currentState以及自己的内部实现逻辑,来计算出新的state,从而更新state。
function dispatch(action) {
// 这里的action要求是一个简单对象,而一个简单对象就是指通过对象字面量和new Object()创建的对象,如果不是就报错。
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
// reducer内部是根据action的type属性来switch-case,决定怎么处理state的,所以type属性是必须的,如果没有type就报错。
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
// 如果是已经在dispatch的,避免连续两次更改state,就报错
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
// 这里就是计算新的state,并赋值给currentState
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// state更新了后,会将注册的回调都触发一遍。大家要注意这里,是都触发一遍哦!
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
subscribe: redux提供的用户一个监听state变化的api,保证当state发生变化时通知到各个组件。
function subscribe(listener) {
// listener是state变化时的回调,必须是个函数
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
// 如果是正在dispatch中,就报错。因为要确保state变化时,监听器的队列也必须是最新的。所以监听器的注册要在计算新的state之前。
if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
)
}
// 标志有订阅的listener
let isSubscribed = true
// 保存一份快照,这个地方还是不能很好的理解
ensureCanMutateNextListeners()
// 添加一个订阅
nextListeners.push(listener)
// 注册监听器后会返回一个取消监听的函数
return function unsubscribe() {
// 如果是已经调用该函数取消监听了,就返回
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
)
}
// 标志已经取消了
isSubscribed = false
//保存一下订阅快照
ensureCanMutateNextListeners()
// 删除
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
combineReducers:这个函数的作用是用来整合多个reducer,在一个实际项目中,不同的reducer处理不同的state,我们当然也可以用一个reducer去处理所有的的state、,但是这样一个reducer中就会包含很多的判断逻辑,很容易产生混乱,不易维护,所以我们最好是将reducer分开写,但是createSrore只能接受一个reducer,所以当有多个reducer时,就需要整合成一个暴露出去。
export default function combineReducers(reducers) {
//先获取传入reducers对象的所有key
const reducerKeys = Object.keys(reducers)
const finalReducers = {} // 最后真正有效的reducer存在这里
//下面从reducers中筛选出有效的reducer
for(let i = 0; i < reducerKeys.length; i++){
const key = reducerKeys[i]
if(typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers);
//这里assertReducerShape函数做的事情是:
// 检查finalReducer中的reducer接受一个初始action或一个未知的action时,是否依旧能够返回有效的值。
let shapeAssertionError
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
//返回合并后的reducer
return function combination(state = {}, action) {
// hasChanged来标志是否计算出了新的state
let hasChanged = false
// 存储新的state
const nextState = {}
// 遍历每一个reducer,把action传进去,计算state
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
//得到该子reducer对应的旧状态
const previousStateForKey = state[key]
//调用子reducer得到新state
const nextStateForKey = reducer(previousStateForKey, action)
// 如果某个reducer没有返回新的state,就报错
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
// 此处来判断是否有新的state,如果所有的子reducer都不能处理action,那么就会返回旧的state,如果能处理就返回新的state
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
// 根据该标志,决定返回原来的state, 还是新的state
return hasChanged ? nextState : state
}
}
applyMiddleware:
redux中间件的作用是用来改造dispatch函数的,由于dispatch是一个纯函数,只能单纯的派发action,有时这样并不能满足我们的需求,比如我们需要打印出每次dispatch的action,或者我们需要处理一个异步的action,这个时候就需要用到像logger、redux-thunk、redux-saga之类的中间件来帮助我们完成这些操作,所以我认为对于redux的精准定义是一个用来加工dispatch的加工厂,要加工什么样的dispatch出来,则需要我们传入对应的中间件函数,先来看一下有无中间件的工作流程:
通过对比,可以明显看到,通过redux中间件对于dispatch的改造,使action得到不同的处理,处理过的action会传递给下一个中间件继续处理,最后一个中间件处理完以后,dispatch一个最终的action给reducer处理
export default function applyMiddleware(...middlewares) {
//这里可以看到applyMiddleware是一个三级柯里化函数,第一个参数是middlewares数组((...middlewares),第二个是createStore,第三个是reducer(...args)
return createStore => (...args) => {
// 利用传入的createStore和reducer和创建一个store
const store = createStore(...args)
let dispatch = store.dispatch
//将store的getState方法和dispatch方法赋值给middlewareAPI
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
// 让每个 middleware 带着 middlewareAPI 这个参数分别执行一遍
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
我觉得redux最难理解的部分就是中间件这一部分,实际项目中用到的中间件不止一个,那么中间件是如何进行串联的呢?在这里我们先自己写一个中间件
const logger = store => next => action => {
console.log('dispatching', action)
return next(action)
}
每一个中间件函数,都接收一个dispatch,然后将改造后的dispatch返回,作为next传递给下一个中间件函数,那么上面的部分
const chain = middlewares.map(middleware => middleware(middlewareAPI))
也就能够理解了,再往下,看过的所有关于redux中间件解析的博客中差不多都会出现这么一句话:这一层只有一行代码,但却是 applyMiddleware 精华所在
dispatch = compose(...chain)(store.dispatch);,
那么他到底精华在哪呢,我们一起看一下。上一行代码中,chan数组中包含了所有中间件处理过的函数(执行到了传入store的地步,那么接下来返回的函数就是等待dispatch的传入),通过compose函数对chin中的函数做处理,组装成一个新的函数,即新的dispatch,假设我们传入的chan=[f1,f2,f3],那么最终我们得到的dispatch是这样的
dispatch = f1(f2(f3(store.dispatch))));
当处理完dispatch以后,我们会将最终的action传递给reducer中,计算出新的state。
总结:
网上讲解redux源码的文章很多,在写这篇文章的过程中我也看过很多其他前辈的文章,写这篇文章的主要目的是在看过源码以后梳理一下自己对这部分的理解,加深自己印象,也为了之后忘了可以快速重温。对于我而言比较难理解的部分是中间件的那一部分,前些日子觉得看懂源码以后就没有再去花时间在这上面,但这两天做项目的过程中发现自己对这部分的理解还是不够透彻,也就写了这篇文章。总的来说,redux的源码部分谈不上多复杂,但更多的是学习他的设计**,这部分代码之所以简洁,我觉得大部分原因还是作者设计的好,而不是逻辑简单。多读读源码,我觉得对于自己编程**的转变是很有帮助的,也能够帮助自己更好的理解这部分知识,从而在项目中更好的使用。
React学习笔记——初识React Hooks篇
自从React提出Hook这个概念到现在已经有一年多的时间了,在Reactv16.8.0版本中正式发布,到目前为止,Hook的特性已经比较稳定,同时Hook的出现也解决了我们长期使用React所带来的的一些痛点,最近在项目中初步接触到React Hooks,所以借着这个时间来细致的学习一下React Hooks。
你还在为该使用无状态组件(Function)还是有状态组件(Class)而烦恼吗?
——拥有了hooks,你再也不需要写Class了,你的所有组件都将是Function。
你还在为搞不清使用哪个生命周期钩子函数而日夜难眠吗?
——拥有了Hooks,生命周期钩子函数可以先丢一边了。
你在还在为组件中的this指向而晕头转向吗?
——既然Class都丢掉了,哪里还有this?你的人生第一次不再需要面对this。
我们常写的React组件一般分为两种,有状态组件和无状态组件,即Class组件和函数组件,函数组件相对于Class组件来说,主要具有这些特性:
- 不需要声明Class, 也就避免了extends constructor等一系列代码
- 不需要显示的声明this,没有声明周期
- 不需要维护一个组件内的状态(state),所有需要的数据都是通过props传进来的
从这些点来看,函数组件无疑是更好的选择,没有任何副作用,组件是可预测的,但在实际开发中,我们因为复杂的业务逻辑而不得不选择Class组件,并且随着项目的不断维护更改,整个组件内部的逻辑也会越来越复杂,代码越来越臃肿,所以这个时候的React Hooks出现了,Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。,这是官方对Hook的解释,按照我自己的理解,它的出现,使得我们能够在函数组件的基础上,维护一个state,并且能够代替我们需要在生命周期中所完成的一些操作。
首先来看一下官方示例
使用Class的示例
import React, { useState } from 'react';
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
使用React Hooks的示例
import React, { useState } from 'react';
function Example() {
// 声明一个叫 "count" 的 state 变量
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
整段代码是不是简洁了很多!组件从Class组件变成函数组件,并且在使用useState这个hook以后,拥有了自己的状态,还可以通过setCount随时改变改变自己的状态。当然,React Hooks除了useState这个hook以外,还有包括useEffect(提供类似生命周期函数的作用), useContext(提供上下文的功能)等一系列其他hook,并且还可以自定义hook来满足我们不同的需求。
React Hook出现的目的
1、在组件之间复用状态逻辑很难
React在组件层面做到了高内聚,低耦合,组件可以说是react的一个核心,一个页面由大大小小不同的许多个组件构成,但是在一个实际项目中,一个组件的代码其实是很长的,加上组件本身的state和声明周期函数中的一些操作,使得我们真正去复用一个组件变的不是很简单,之前官方推荐的解决方法是渲染属性(Render Pros)和高阶组件(HOC)
渲染属性指的是使用一个值为函数的 prop 在 React 组件之间的代码共享。说白了就是通过传递props给一个组件以后返回一个组件给我们,就像这样
:
<DataProvider>
<Component data={data} />
</DataProvider>
至于高阶组件其实就是一个函数,一个以组件为参数,并且返回一个新的组件的函数。说白了就是一个函数对传入的组件进行了处理以后在返回,就像这样
function logProps(InputComponent) {
InputComponent.prototype.componentWillReceiveProps = function(nextProps) {
console.log('Current props: ', this.props);
console.log('Next props: ', nextProps);
};
// 返回原始的 input 组件,暗示它已经被修改。
return InputComponent;
}
// 每次调用 logProps 时,增强组件都会有 log 输出。
const EnhancedComponent = logProps(InputComponent);
这两种模式乍看起来确实觉得不错,作为一个小白,说实话我一直写的也挺爽的,但是使用这两种设计模式,会不可避免的增加我们的代码层级,导致组件嵌套过深
,随便打开一个网页都能看到这种嵌套很深的dom结构,所以随着今天的深入,也发现了这两种模式并非最优解。
2、复杂组件变得难以理解
写了也有接近半年的React了,但是直到现在,除了最常用的几个生命周期函数以外,每次用到其他的生命周期函数,我都要去查一下生命周期表单来确认有没有写错,更不用说复杂组件中生命周期函数的使用了,我们需要在生命周期中实现获取数据,监听事件,取消订阅等等一系列的逻辑,随着组件的不断维护扩充,组件也就变得越来越难以理解
3、难以理解的 class
class中最难以理解的应该是this了吧,为了保证this的指向符合预期,this.funtion = this.funtion.bind(this)写了无数遍, <div onClick={(data) => this.function(data)}> </div>
也经常会写,数不清的this写起来确实很麻烦
为了消灭这些开发过程中的问题,Hooks来了!(他来了他来了,他踩着祥云走来了!)
如何使用React Hooks
export default function CountDown(props) {
const tmr = useRef(0);
const [stop, setStop] = useState(false);
const [stopTime, setStopTime] = useState(0);
const [refresh, setRefresh] = useState(props.refresh);
const handleRefresh = useCallback(() => {
clearTimeout(tmr.current);
tmr.current = setTimeout(() => {
setRefresh(refresh - 1);
if (refresh == 0) {
const { onFinish } = props;
onFinish && onFinish();
setRefresh(props.refresh);
}
}, 1000);
}, [setRefresh, refresh, props]);
const handleClick = useCallback(() => {
if (!stop && refresh > 0) {
clearTimeout(tmr.current);
setStop(true);
setStopTime(refresh);
} else if (stop) {
setStop(false);
// setRefresh(stopTime);
handleRefresh();
}
}, [stop, refresh, stopTime, setRefresh]);
useEffect(() => {
handleRefresh();
}, [refresh]);
return (
<div className={styles.wrapper}>
{
stop ? (
<div onClick={handleClick} >
{
stopTime < 10 ? '0' + stopTime : stopTime
}
</div>
) : (
<div onClick={handleClick}>
{
refresh < 10 ? '0' + refresh : refresh
}
</div>
)
}
</div>
);
}
这是一个倒计时组件,当倒计时结束时会调用回调函数,执行然后重新计时,让我们一起看一下它是怎么实现的吧:
首先,声明我们需要维护的变量stop、stopTime、refresh,声明tmr时注意,如果我们要声明一个普通变量,最好使用useRef来声明,声明以后初始值会存储在tmr.current中,以后每次修改也都是修改tmr.current,这样做的好处是能够确保tmr不会丢失,因为整个函数每次都会完全重新渲染,所以很容易造成我们最新渲染出来的变量没有指向先前的修改从而导致bug的产生, 接下来,使用useCallback来声明两个我们需要用到的函数分别用来处理点击事件和回调事件。useCallback(() => {},[]), []中一定要将我们函数中需要用到的依赖也就是用到的变量声明,这里的目的也是为了数据的指向精准,最后声明useEffect(() => {},[]) []这里的数组里面需要写什么呢? 你希望当哪个变量发生变化时执行这个useEffect呢? 希望哪个就写入哪个, 如果写入一个空数组,那么作用就相当于componentDidMount只会执行一次,如果像这样什么也没有写,useEffect(() => {}),那么每次组件重新渲染时,这个useEffect都会执行
总结,React Hooks的出现,极大的解决了我们目前在用react实现复杂项目时的一些痛点,但是里面的坑也比较多,这篇文章只是初步科普一下React Hooks的使用,至于更深一步比如为什么会有精准依赖,Hooks的数据是怎么保持的等等要抽出更多的时间来了解Hooks的源码实现,未完待续~
React学习笔记——性能优化篇
实习阶段有幸参与了团队内部平台的建设,某天在进行页面的迭代更新时发现页面有太多次没意义的render,众所周知,重排是非常浪费页面性能的,所以应该尽可能避免这种情况,所以用了一下午的时间对平台页面进行了优化,并写下这篇文章来记录一下学习过程。
react给了开发者极大的自由,只提供了核心的API,但是自由的同时也伴随着代码混乱,虽然react提供了强大的虚拟dom来帮助我们提高web性能,但是谁不想自己的页面加载的更快呢,我主要是使用了生命周期函数shouldComponentUpdate来进行性能优化:
1、浅比较避免重复渲染
当一个组件的props或者state发生了改的时候,react会借助vdom对比前后变化,当dom发生变化时,react会更新dom,当父组件重新渲染时,伴随着子组件也会重新渲染,试想一下,如果一个组件内部有1,000个子组件,那会浪费多少性能。所以在一些情况下,你可以通过shouldComponentUpdate这个生命周期函数来阻止react更新dom,减少render的次数。这个函数在重新渲染之前触发,默认返回true,当返回false时,会阻止这次渲染。引用官网的一张图来进一步说明
SCU表示shouldComponentUpdate,绿色表示返回true(需要更新),红色表示返回false(不需要更新);vDOMEq表示虚拟DOM比对,绿色表示一致(不需要更新),红色表示发生改变(需要更新)
根据渲染流程,首先会判断shouldComponentUpdate(SCU)是否需要更新。如果需要更新,则调用组件的render生成新的虚拟DOM,然后再与旧的虚拟DOM对比(vDOMEq),如果对比一致就不更新,如果对比不同,则根据最小粒度改变去更新DOM;如果SCU不需要更新,则直接保持不变,同时其子元素也保持不变。
C1根节点,绿色SCU、红色vDOMEq,表示需要更新。
C2节点,红色SCU,表示不需要更新,同时C4、C5作为其子节点也不需要检查更新。
C3节点,绿色SCU、红色vDOMEq,表示需要更新。
C6节点,绿色SCU、红色vDOMEq,表示需要更新。
C7节点,红色SCU,表示不需要更新。
C8节点,绿色SCU,表示React需要渲染这个组件;绿色vDOMEq,表示虚拟DOM一致,不更新DOM。
因此,我们可以根据我们组件的实际情况,在真正需要重新渲染的时候再返回true,其他时候都返回false。shouldComponentUpdate有两个参数nextState,nextProps,一般情况下我们是根据这两个值的变化情况选择返回true还是false。像下面这段代码。
class CounterButton extends React.Component {
constructor(props) {
super(props);
this.state = {count: 1};
}
shouldComponentUpdate(nextProps, nextState) {
if (this.props.color !== nextProps.color) {
return true;
}
if (this.state.count !== nextState.count) {
return true;
}
return false;
}
render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}
在以上代码中,shouldComponentUpdate只检查props.color和state.count的变化。如果这些值没有变化,组件就不会更新。当你的组件变得更加复杂时,你可以使用类似的模式来做一个“浅比较”,用来比较属性和值以判定是否需要更新组件。这种模式十分常见,因此React提供了一个辅助对象来实现这个逻辑 - 继承自React.PureComponent。
大部分情况下,你可以使用React.PureComponent而不必写你自己的shouldComponentUpdate,但是它只做一个浅比较,当你比较的目标为引用类型数据,变量引用地址是没有改变的,引用数据发生了改变,浅比较就显得优点乏力了。
2、使用不可突变数据避免重复渲染
引用官网中的例子解释一下突变数据产生的问题。例如,假设你想要一个ListOfWords组件来渲染一个逗号分隔的单词列表,并使用一个带了点击按钮名字叫WordAdder的父组件来给子列表添加一个单词。以下代码并不正确:
class ListOfWords extends React.PureComponent {
render() {
return <div>{this.props.words.join(',')}</div>;
}
}
class WordAdder extends React.Component {
constructor(props) {
super(props);
this.state = {
words: ['marklar']
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// 这段内容将会导致代码不会按照你预期的结果运行
const words = this.state.words;
words.push('marklar');
this.setState({words: words});
}
render() {
return (
<div>
<button onClick={this.handleClick} />
<ListOfWords words={this.state.words} />
</div>
);
}
}
导致代码无法正常工作的原因是 PureComponent 仅仅对 this.props.words的新旧值进行“浅比较”。在words值在handleClick中被修改之后,即使有新的单词被添加到数组中,但是this.props.words的新旧值在进行比较时是一样的(引用对象比较),因此 ListOfWords 一直不会发生渲染。
避免此类问题最简单的方式是避免使用值可能会突变的属性或状态,如:
数组使用concat
handleClick() {
this.setState(prevState => ({
words: prevState.words.concat(['marklar'])
}));
}
对象使用Object.assign()
handleClick() {
// 假设我们有一个叫colormap的对象,下面方法不污染原始对象
this.setState(prevState => ({
words: Object.assign({}, colormap, {right: 'blue'})
}));
}
数组和对象都可以使用的JSON.parse(JSON.stringify(something))和解构赋值
handleClick() {
this.setState(prevState => ({
words: JSON.parse(JSON.stringify(prevState))
}));
}
handleClick() {
this.setState(prevState => ({
words: [...prevState.words, 'marklar'],
}));
};
或者使用不可突变数据immutable.js
immutable.js使得变化跟踪很方便。每个变化都会导致产生一个新的对象,因此我们只需要检查索引对象是否发生改变。
const SomeRecord = Immutable.Record({ foo: null });
const x = new SomeRecord({ foo: 'bar' });
const y = x.set('foo', 'baz');
x === y; // false
在这个例子中,x突变后返回了一个新的索引,因此我们可以安全的确认x被改变了。
不可突变的数据结构帮助我们轻松的追踪对象变化,从而可以快速的实现shouldComponentUpdate。
推荐一个react性能检测工具
react16版本之前,我们可以使用react-addons-perf工具来查看,而在最新的16版本,我们只需要在url后加上?react_pref。
首先来了解一下react-addons-perf。
react-addons-perf这是 React 官方推出的一个性能工具包,可以打印出组件渲染的时间、次数、浪费时间等。
简单说几个api,具体用法可参考官网:
Perf.start() 开始记录
Perf.stop() 结束记录
Perf.printInclusive() 查看所有设计到的组件render
Perf.printWasted() 查看不需要的浪费组件render
再来了解一下,react16版本的方法,在url后加上?react_pref,就可以在chrome浏览器的performance中查看了。
【译】为什么TypeScript是2019年编写前端的最佳方式
TypeScript在前端环境中越来越受欢迎。已经有80%的开发人员承认他们想在下一个项目中使用或学习TypeScript。自从我第一次使用它以来,我就一直喜欢它。我肯定会在以后的所有项目中继续使用它。
有些人担心TypeScript是前端工具链的不必要依赖。是吗?跟着我来了解现实是完全相反的。
一个“动态类型语言专家”的故事
在我编程生涯的18年中,我从未喜欢过静态类型的语言。自从2001年开始编程以来,我一直选择动态选择语言:PHP,JS,Ruby,Elixir。我到处都用C ++和Java编写了一些程序,但是我一直讨厌那些环境。(“为什么我需要在各处传递类型?这太糟了。我自己可以照顾他们。”)
一切都在2年前发生了变化。那是我第一次使用TypeScript的时候。但是,从一开始我就没有爱上它。在最初的几天里,这实际上让我很烦。情况很快改变了。我在代码中输入的类型定义越多,我越经常注意到它使我免于浪费时间在控制台中手动调试愚蠢的bug。我开始欣赏这些类型。
在接下来的两年中,无论何时我在前端应用程序上进行协作,无论是Angular还是React,我都注意到,无论我使用什么框架,在所有.js项目中总会遗漏一件事:TypeScript 。
现在我必须承认。我不喜欢动态类型的语言了。我仍然可以在其中高效地工作,但是当我不能只在IDE中查找类型定义或在对代码进行原型设计时信任编译器时,这使我感到不满意。(我在Elixir中唯一仍然想念的是强类型。)
幸运的是,我们不必等到Ecma TC39提交者将静态类型系统引入JavaScript。我们可以改用TypeScript。特别是在新项目中,使用它的麻烦极少。
反TypeScript参数
尽管如此,仍然有人敢于争辩说将TypeScript引入您的项目将:
- 增加新开发人员的入职时间,
- 维护复杂化
- 与React发生很多冲突,
- 增加开发时间,
- 将项目锁定到一年后将不再存在的某些时髦技术中,
- 阻止招募优秀的JS人员,
- 使得不可能从非TS代码库中引入代码,
- 使得在遥远的将来很难编辑该应用。
我敢说他们都错了。TypeScript将为您解决以上所有情况,我可以为您提供具体的论据,为什么会这样。
这就是为什么我决定写这篇文章的原因。也许这将有助于说服您,您的朋友,同事或CTO使用这种出色的语言。
注意:在本文中,我不会解释“ TypeScript是什么”。我仅关注“ 为什么”您应该使用它。如果您仍然不了解TypeScript的真正含义,建议您首先阅读以下一些链接:
- “什么是TypeScript,为什么我要用它代替JavaScript?” — Lodewijk的Bogaards,StackOverflow
- TypeScript语言官方网站
- “ TypeScript —具有超能力的JavaScript” — Indrek Lasn,中等
- “ TypeScript深入学习” — Basarat Ali Syed撰写的电子书
TypeScript的优势
1.代码更容易理解
通常,当您处理一段代码(例如功能代码)时,要完全理解它,您需要掌握以下内容:
- 它接受什么论点?
- 它返回什么值?
- 需要什么外部数据?
- 为了产生返回值,这些参数和外部数据有什么作用?
在动态类型语言中,通常很难回答前三个问题。如果一个函数收到article
参数,那么它到底是什么?它是具有某些商品属性的对象吗?有哪些确切的属性?是否有一个article.title
或article.name
?我可以始终假设article.title
存在吗?怎么article.isPublished
样我可能知道此属性article在应用程序的大多数位置都已合并到对象中,但是我可以确定,它始终也存在于此位置吗?
要回答所有这些问题,通常需要执行以下操作之一:
- console.log(article)在浏览器中放置一个,运行脚本,(也许单击一下UI),然后阅读日志;
- 查看该函数的使用位置,并从那里跟踪将哪些数据放入所有出现的位置;
- 向您的同事询问最近是否正在使用此代码(希望他们仍然健在,在线并记住该代码);
- 假设它article与您所想的一样,只是希望它能起作用。
这听起来对您熟悉吗?
对我来说,这听起来像是任何动态类型化语言(例如JS,PHP,Ruby,Python,Elixir)中的典型Web开发工作流程。
在诸如TypeScript之类的静态类型语言中,您可以立即从IDE和编译器中获得上述所有问题的答案。您不再需要查看整个代码库,让同事困扰于问题,也不必冒生产中的错误的风险。
2.代码更容易实现
通常,当您必须创建新功能或新组件时,您的工作流程可能如下所示:
- 引导组件函数,组成其构造函数参数,编写其余代码。
- 如果它需要任何外部或复杂的数据(如user或articles),请猜测它的外观,将其保存在自己的内存中,并像在代码中那样使用它。
- 将组件放到您的应用中,然后将道具传递给它。
- 对其进行手动或单元测试。(您需要确保它收到了它应该拥有的道具,并确保它应该如何工作。)
- 如果有什么不对劲,请返回您的代码,尝试找出问题所在,进行修复,然后返回步骤no。4。
在TypeScript中,它是相似的,但是更容易,更快捷:
- 引导组件功能,定义其类型定义,并实现它。
- 如果需要任何外部或复杂数据,请查找它们的接口并重用它们(全部或部分)。
- 将组件放到您的应用中,然后将道具传递给它。
- 而已。(如果您在调用方和被调用方之间正确地匹配了typedef,则所有内容都应该可以正常工作。现在唯一需要测试的就是组件的实际业务逻辑。)
因此,每当您以TypeScript编写代码时,它不仅更具可读性且不易出错,而且主要是,更易于推理。
3.代码更易于重构
您经常需要重构很多东西,但是由于它们涉及很多东西和文件,因此您太害怕更改它们了。
在TypeScript中,通常只需在IDE中单击“ 重命名符号 ”命令即可重构这些东西。
在动态类型的语言中,可以帮助您同时重构多个文件的最佳方法是使用RegExp搜索和替换。
在静态类型语言中,不再需要“搜索和替换”。使用诸如“查找所有事件”和“重命名符号”之类的IDE命令,您可以在应用程序中查看给定功能,类或对象接口属性的所有事件。
每当您想要稍微改善构建系统,重命名组件,更改user对象或删除不推荐使用的属性时,您都不必担心会再发生问题。TypeScript将帮助您查找重构位的所有用法,重命名它,并在重构后代码有任何类型不匹配的情况下向您发出编译错误警告。
4.更少的错误
在多年的前端Web开发中,我注意到,只要有一个人坐在我旁边,每当我做错字时都会对我大喊大叫,我可以节省大约50%的错误修复时间。可能为null的值,或者将对象传递到应该为数组的位置。
我很高兴地说,我终于遇到了那个伙伴:它叫做TypeScript。
多亏了它,现在编写无效代码变得更加困难。如果编译成功,您可能会确定它确实有效。
5.更少的样板测试
当您确定将变量正确地传递到所有给定位置时,就无需再对所有变量进行测试了。
与编写简单的样板单元/集成测试不同,您可以将更多的精力放在测试应用程序的业务逻辑上,而不是测试函数参数是否在彼此之间正确传递。
更少的测试意味着更短的时间来开发新功能,以及更小的代码库,从而减少了代码的复杂性,出错率并且易于维护。
6.代码更易于合并
您团队中的新初级开发人员刚刚发布了PR,引入了新代码。乍一看,一切都还不错:代码看起来不错,单元测试在那里,一切都通过了绿色。
您现在可以确定它可以正常工作吗?如果没有适当的单元测试该怎么办?(是的。让我们认识现实中的人们,我们很多人仍然没有写足够的数量。)您会信任公关创作者吗?还是您会花费宝贵的5分钟时间自行运行代码并进行测试?
如果您的工具链中包含TypeScript,它将为您提供另一项保证检查:typedef编译检查。
如果代码看起来不错,就可以进行单元测试,并且整个程序都可以编译,现在您可以确定整个程序可以正常工作。
使用TypeScript可以更轻松地信任其他开发人员。它可能会提高您查看和合并PR的速度。
(反之亦然:由于类型定义,对于新开发人员而言, 无需深入研究或自己运行代码,就更容易看到其他人的代码部分真正在做什么。)
7.帮助开发人员拥有正确的工作流程
用静态类型的语言编写代码时,首先需要考虑将要接收的数据类型,然后考虑要生成的数据类型。通常只有在那之后,您才坐下来进行实际的实现。
许多人会赌命,这是正确的编码工作流程。
例如,无论何时开发算法,都应首先考虑其理论公式,然后加以实施。
或者,无论何时进行TDD,您首先需要考虑代码在现实中的工作方式(它将接收什么数据以及它将产生什么数据),将其编写为测试,然后实施实际代码。
同样适用于TypeScript。它鼓励您在坐下来执行代码的内部实现之前,先考虑一下代码的接口。
TypeScript缺点
1.需要编译步骤
我的一些后端Node.js朋友告诉我,为他们介绍TS只是不值得的,因为在将.ts它们运行在Node上之前,需要对所有文件进行预编译会带来很多麻烦。
虽然可以肯定您可以通过良好的构建和开发设置来处理该问题,但我不能不同意它会为您的Node.js应用程序增加一些开销。
我在前端环境中对此表示不同。如今,每个人都在编译JS。您需要旧版浏览器支持吗?ES7的功能?CSS-in-JS?对于所有这些,您可能已经使用了babel。可以使用Babel编译TypeScript,就像JS的任何其他语法(包括ES7或JSX)一样。
将TypeScript带到您的前端项目中几乎不会给构建设置带来任何开销。(仅在根本不编译JS的情况下,这可能会带来开销,但是这种情况很少发生在前端。)
2.设置有点困难
我可以同意这一点。例如,TypeScript中的Next.js应用程序和Next.js应用程序之间有什么区别?在第二种情况下,您需要使Node.js服务器,webpack和jest测试运行程序能够与TypeScript一起使用。另外,每当添加诸如React,Redux,Styled-Components之类的库时,还需要为其添加typedef,例如npm i @types/styled-components(除非lib中包含TS typedefs文件)。
您需要回答自己的问题是,您多久做一次这样的事情?值得放弃TypeScript的所有优势吗?
摘要
我是说我们所有人都应该突然切换到TypeScript吗?不。例如,在一个现有项目中切换到它肯定是很多工作,并且在这样做之前,应该认真考虑一下。
但是,如果您要创建一个新的前端应用程序(必须随时间进行维护),那么情况就很清楚了。使用TypeScript。老实说,我很想听听在您的下一个前端项目中使用TS的原因是什么。
我只想这样说:通过使用TypeScript,您将获得许多强大的优势,而一开始只需付出很少的精力。
让我们来做TypeScript的人吧
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.