Giới thiệu về Reactive Programing trong javascript

Công Nghệ
Giới thiệu về Reactive Programing trong javascript
Bài viết được sự cho phép của tác giả Lưu Bình An Reactive programing là khái niệm khá trừu tượng và khó tiếp cận với người mới bắt đầu, chuẩn bị tinh thần đọc bài này vài lần trong vài ngày thì mới mong thẩm thấu hết. Reactive programing là gì? Reactive programming is programming with asynchronous data streams Tạm dịch: Reactive programming là lập trình xử lý với dữ liệu không tuần tự (async) như stream Có khái niệm mới stream Muốn hiểu được reactive programing, bạn cần biết khái niệm stream Tìm việc làm Javascript lương cao trên Station D Stream là gì? Có thể hình dung stream như là một array đặc biệt , chứa một tập các phần tử đặc biệt , các phần tử này có thể emit: 1. value, 2. error, 3. complete, các phần tử trong stream cũng không có hết ngay từ đầu, mà sẽ xuất hiện ở một thời điểm ko xác định trong tương lai. Về sau, mình dùng kiểu viết này để mô tả stream --a---b-c---d---X---|-> a, b, c, d là các value được emit X error | completed signal ---> dòng thời gian Tuân theo Observer Design Pattern , việc lắng nghe stream gọi là subscribe , những gì được emit, chúng ta viết các function để xử lý cho 3 trường hợp, các function này gọi là observer Ví dụ, trên giao diện, chuỗi các event click trên một trang có thể được xem là một stream Trên stream click ban đầu, chúng ta thực hiện một số thao tác, nếu click trong khoảng 250ms gộp lại thành 1, filter để chỉ lấy các data lớn hơn 2. Những hàm để xử lý các data stream như vậy gọi là operator Có rất nhiều thứ có thể...
Bài viết được sự cho phép của tác giả Lưu Bình An
Reactive programing là khái niệm khá trừu tượng và khó tiếp cận với người mới bắt đầu, chuẩn bị tinh thần đọc bài này vài lần trong vài ngày thì mới mong thẩm thấu hết.

Reactive programing là gì?

Reactive programming is programming with asynchronous data streams

Tạm dịch: Reactive programming là lập trình xử lý với dữ liệu không tuần tự (async) như stream

Có khái niệm mới stream

Muốn hiểu được reactive programing, bạn cần biết khái niệm stream

Tìm việc làm Javascript lương cao trên Station D

Stream là gì?

Có thể hình dung stream như là một array đặc biệt, chứa một tập các phần tử đặc biệt, các phần tử này có thể emit: 1. value, 2. error, 3. complete, các phần tử trong stream cũng không có hết ngay từ đầu, mà sẽ xuất hiện ở một thời điểm ko xác định trong tương lai.

Giới thiệu về Reactive Programing trong javascriptGiới thiệu về Reactive Programing trong javascript

Về sau, mình dùng kiểu viết này để mô tả stream

--a---b-c---d---X---|->

a, b, c, d là các value được emit
X error
| completed signal
---> dòng thời gian

Tuân theo Observer Design Pattern, việc lắng nghe stream gọi là subscribe, những gì được emit, chúng ta viết các function để xử lý cho 3 trường hợp, các function này gọi là observer

Ví dụ, trên giao diện, chuỗi các event click trên một trang có thể được xem là một stream

Giới thiệu về Reactive Programing trong javascript

Trên stream click ban đầu, chúng ta thực hiện một số thao tác, nếu click trong khoảng 250ms gộp lại thành 1, filter để chỉ lấy các data lớn hơn 2. Những hàm để xử lý các data stream như vậy gọi là operator

Có rất nhiều thứ có thể xem là async data stream. Ví dụ: một cái form đăng ký với các input username, password, email, nút submit, nguyên quá trình user nhập giá trị các field này đến lúc submit, là một async data stream. Một giao diện counter, có duy nhất một button ấn để tăng counter, thì suốt quá trình ấn counter được xem là async data stream.

Để làm việc với Reactive Programing, 100% bạn cần dùng đến thư viện (siêu nhân có thể tự viết), tùy theo ngôn ngữ (ko chỉ có javascript mới có nhé), nó sẽ có một số hàm để bạn chuyển đổi một data bình thường thành một data stream (data stream là phải có thể emit 3 cái đã nói), một số hàm để bạn mergeflattenfilter các data stream này lại.

Tại sao chúng ta cần Stream + Reactive Programing

Có thể thấy ngay Reactive programing khá trừu tượng, nhưng do thay vì implement những ràng buộc một cách chi tiết, những ràng buộc này được gắn vào từng data gửi đi trên stream, code nó sẽ gọn gàng hơn.

Kiểu viết này sẽ mang phong cách declarative hơn là imperative, chúng ta không khai báo từng bước tuần tự cần làm gì, chúng ta chỉ khai báo mối quan hệ giữa các stream với nhau.

Giới thiệu về Reactive Programing trong javascriptGiới thiệu về Reactive Programing trong javascript

10 năm trước, mọi việc chỉ đơn giản là submit toàn bộ giá trị các field lên backend xử lý, rồi đơn thuần hiển thị kết quả trả về, bây giờ user thích real-time feedback, bấm “like” một phát là đầu bên kia thấy được liền.

Những event real-time như thế, user khoái, chúng ta cần có một công cụ lập trình để làm việc đó, Reactive Program ra đời cũng từ yêu cầu của user.

Implement hộp thoại “Who to follow” của twitter

Mình sẽ sử dụng RxJS trong ví dụ, vì mình chỉ biết javascript thôi các bạn.

Giới thiệu về Reactive Programing trong javascript

Tính năng chính của hộp thoại này

  • Vừa mở lên, load data từ API, hiển thị 3 tài khoản
  • Click “Refresh”, hiển thị 3 tài khoản khác
  • Khi click “x”, xóa tài khoản đó khỏi danh sách, hiển thị một tài khoản khác.

Chúng ta tiếp cận với vấn đề này như thế nào, gần như mọi thứ có thể xem là stream.

Load dữ liệu lúc đầu

Bắt đầu với tính năng đơn giản nhất “Mới vào, load 3 account từ API”. (1) gửi 1 request (2) nhận response (3) render kết quả

Lúc bắt đầu chúng ta chỉ có 1 request, mọi thứ rất đơn giản, yên tâm là nó sẽ phức tạp dần lên khi có nhiều request. Mô phỏng nó như data stream, stream này chỉ có 1 emit value.

——a——-|—>

Khi có một event request xảy ra, nó báo 2 việc: khi nào và cái gì. Khi nào event này được emit và cái gì chính là value được emit (url string)

Trong Rx, bà con gọi stream là Observable, mình thích gọi là stream hơn

var requestStream = Rx.Observable.just('https://api.github.com/users');

Khi emit value, chúng ta subscribe để thực thi một hành động tiếp theo

requestStream.subscribe( requestUrl => {
// execute the request
  jQuery.getJSON(requestUrl, function(responseData) {
    // ...
  });
}

Cái response của request cũng là một dạng stream, dữ liệu sẽ đến tại một thời điểm không xác định trong tương lai

requestStream.subscribe(function(requestUrl) {
  // execute the request
  var responseStream = Rx.Observable.create(function (observer) {
    jQuery.getJSON(requestUrl)
    .done(function(response) { observer.onNext(response); })
    .fail(function(jqXHR, status, error) { observer.onError(error); })
    .always(function() { observer.onCompleted(); });
  });
  
  responseStream.subscribe(function(response) {
    // do something with the response
  });
}

Rx.Observable.create() sẽ tạo ra những stream mới, qua việc thông báo cho các observer đang subscriber các sự kiện onNext()onError().

Nó giống cách chạy của Promise lắm đúng không? Vâng Observable là một dạng Promise++, phiên bản mở rộng.

Chúng ta có 1 subscribe bên trong 1 subscribe khác, nó giống như callback hell. Thêm nữa việc tạo responseStream hoàn toàn độc lập với requestStream. Trong Rx chúng ta có một cách đơn giản để transform và tạo một stream mới từ những thằng khác

Hàm map(f), sẽ lấy từng giá trị của stream A, gọi function f(), và trả về giá trị cho stream B. Tạo một stream này từ stream khác, y như hàm map của array thôi mà.

var responseMetastream = requestStream
  .map(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

Sau đó chúng ta tạo một stream của stream metastream. Bắt đầu phức tạp rồi đó. Metastream là 1 stream mà mỗi cái value được emit sẽ trỏ ra 1 stream khác. Trong ví dụ, mỗi URL request, được trỏ đến một stream promise chứa response

stream của stream - metastream

Với responseStream, chúng ta chỉ một đơn giản một stream chứa response, nên việc tạo một metastream cho response sẽ rối và không cần. Mỗi giá trị được emit của response sẽ là một object JSON, không phải một Promise của object JSON. Sử dụng .flatMap() để gộp tất cả response thành 1 stream, .flatMap là operator để xử lý dữ liệu async trong Rx

var responseStream = requestStream
  .flatMap(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

flatMap để giảm số chiều của stream

responseStream được khai báo bởi requestStream, nếu sau này có thêm các sự kiện trên requestStream, chúng ta sẽ có một event response tương ứng trên responseStream

requestStream:  --a-----b--c------------|->
responseStream: -----A--------B-----C---|->

Sau khi có được responseStream, chúng ta render thôi

responseStream.subscribe(function(response) {
  // render `response` to the DOM however you wish
});

Toàn bộ bode bây giờ

var requestStream = Rx.Observable.just('https://api.github.com/users');

var responseStream = requestStream
  .flatMap(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

responseStream.subscribe(function(response) {
  // render `response` to the DOM however you wish
});

Nút refresh

JSON trả về từ API sẽ có 100 user, nó chỉ cho thêm offset, không cho set page size, chúng ta chỉ cần 3 user, lãng phí hết 97 user. Tạm thời không quan tâm phần này, chúng ta sẽ cache lại cái response sau.

Khi click nút refresh, requestStream sẽ emit một URL mới, sau đó chúng ta nhận được một response mới. Chúng ta cần 2 thứ:

  • 1 stream cho sự kiện click -> refreshStream
  • cập nhập lại requestStream để nó phụ thuộc vào refreshStream

RxJS có hàm để chuyển event thành stream

var refreshButton = document.querySelector('.refresh');
var refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click');

Click refresh nó không có URL kèm theo, chúng ta phải nhét cái URL bằng code. Map vào URL với giá trị offset ngẫu nhiên

var requestStream = refreshClickStream
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  });

Tới đây, chắc chắn mở app lên không thấy gì cả, không có request nào được gửi đi, chỉ click refresh thì mới thấy.

Phải tách stream này ra riêng

var requestOnRefreshStream = refreshClickStream
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  });
  
var startupRequestStream = Rx.Observable.just('https://api.github.com/users');

Sau đó mới .merge() lại

stream A: ---a--------e-----o----->
stream B: -----B---C-----D-------->
          vvvvvvvvv merge vvvvvvvvv
          ---a-B---C--e--D--o----->
var requestOnRefreshStream = refreshClickStream
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  });
  
var startupRequestStream = Rx.Observable.just('https://api.github.com/users');

var requestStream = Rx.Observable.merge(
  requestOnRefreshStream, startupRequestStream
);

Có cách gọn hơn, không cần đến một stream trung gian

var requestStream = refreshClickStream
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  })
  .merge(Rx.Observable.just('https://api.github.com/users'));

Thậm chí gọn hơn nữa

var requestStream = refreshClickStream
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  })
  .startWith('https://api.github.com/users');

Chủ ý nãy giờ là giải thích .startWith() đó. Tuy nhiên là còn có thể tốt hơn nếu chúng ta không lặp lại URL. Làm việc đó bằng cách dời thằng startWith() ngay sau refreshClickStream, để giả lập sự kiện refresh khi vừa mới mở

var requestStream = refreshClickStream.startWith('startup click')
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  });

Khi click nút refresh, chúng ta cũng sẽ remove 3 thằng user đang hiển thị, như vậy chúng ta sẽ subscribe trên refreshClickStream

refreshClickStream.subscribe(() => {
  // clear 3 sugesstion
})

Tuy nhiên, responseStream cũng đang có 1 subscribe ảnh hướng đến việc render, như vậy việc render này cũng tạo thêm 1 stream (có 2 sự kiện emit value để render)

var suggestion1Stream = responseStream
  .map(function(listUsers) {
    // get one random user from the list
    return listUsers[Math.floor(Math.random()*listUsers.length)];
  });

Chúng ta cũng sẽ có suggestion2Streamsuggestion3StreamsuggestionNStream hoàn toàn giống với suggestion1Stream, nhưng mình sẽ để các bạn tự suy nghĩ cách giải quyết. Ví dụ này chỉ đề cập đến suggestion1Stream

Thay vì render trên subscribe của responseStream

suggestion1Stream.subscribe(function(suggestion) {
  // render the 1st suggestion to the DOM
});

Quay lại vấn đề “click refresh, xóa suggestion”, chúng ta đưa vào sugesstion1Stream giá trị null khi refresh

var suggestion1Stream = responseStream
  .map(function(listUsers) {
    // get one random user from the list
    return listUsers[Math.floor(Math.random()*listUsers.length)];
  })
  .merge(
    refreshClickStream.map(function(){ return null; })
  );

Với trường hợp null, đơn giản render thông báo

suggestion1Stream.subscribe(function(suggestion) {
  if (suggestion === null) {
    // hide the first suggestion DOM element
  }
  else {
    // show the first suggestion DOM element
    // and render the data
  }
});

Hình dung quá trình này như sau, trong đó N là giá trị null

refreshClickStream: ----------o--------o---->
     requestStream: -r--------r--------r---->
    responseStream: ----R---------R------R-->   
 suggestion1Stream: ----s-----N---s----N-s-->
 suggestion2Stream: ----q-----N---q----N-q-->
 suggestion3Stream: ----t-----N---t----N-t-->

Click đóng một suggestion

Khi user click vào nút “x”, chúng ta sẽ load 1 user khác vào. Cách chúng ta nghĩ đến đầu tiên, tạo một request mới khi click vào nút “x”

var close1Button = document.querySelector('.close1');
var close1ClickStream = Rx.Observable.fromEvent(close1Button, 'click');

var requestStream = refreshClickStream.startWith('startup click')
  .merge(close1ClickStream) // merge với close stream
  .map(function(){
    var randomOffset = Math.floor(Math.random()*500);
    var 'https://api.github.com/users?since=' + randomOffset;
  })

Không chạy, nó sẽ remove user và tải mới 3 suggestion luôn. Vì cái API của chúng ta xài nó load 1 lần 100 user, nên giờ chúng ta chỉ lấy các user nào chưa hiển thị luôn, không cần refresh mới.

Suy nghĩ theo hướng stream, khi event close1 xuất hiện, chúng ta lấy emit response mới nhất trên responseStream, rồi lấy ngẫu nhiên 1 user

  requestStream: --r--------------->
   responseStream: ------R----------->
close1ClickStream: ------------c----->
suggestion1Stream: ------s-----s----->

Operator là combineLatest sẽ nhận vào 2 stream A, B, khi 1 trong 2 stream có emit value, combineLatest sẽ join 2 value emit gần nhất ab rồi trả về c = f(x, y), trong đó f là function chúng ta khai báo

stream A: --a-----------e--------i-------->
stream B: -----b----c--------d-------q---->
          vvvvvvvv combineLatest(f) vvvvvvv
          ----AB---AC--EC---ED--ID--IQ---->

Chúng ta có thể áp dụng combineLatest() cho close1ClickStream và responseStream, như vậy khi click nút close, nó sẽ lấy kết quả mới nhất từ response rồi trả về một giá trị mới cho suggestion1Stream

var suggestionStream = close1ClickStream
  .combineLatest(responseStream, function(click, listUsers) {
    return listUsers[Math.floor(Math.random()*listUsers.length)];
  })
  .merge(
    refreshClickStream.map(function() {return null;})
  )
  .startWith(null);

Còn vấn đề nhỏ xíu nữa là, combineLatest chỉ chạy khi cả 2 stream đã có giá trị, nếu 1 trong 2 stream chưa emit value nào hết, thì nó không chạy. Để giải quyết vấn đề này, chúng tả giả lập click close1 khi vừa mở app

var suggestion1Stream = close1ClickStream.startWith('startup click') // we added this
  .combineLatest(responseStream,             
    function(click, listUsers) {l
      return listUsers[Math.floor(Math.random()*listUsers.length)];
    }
  )
  .merge(
    refreshClickStream.map(function(){ return null; })
  )
  .startWith(null);

Tổng kết

Toàn bộ code

var refreshButton = document.querySelector('.refresh');
var refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click');

var closeButton1 = document.querySelector('.close1');
var close1ClickStream = Rx.Observable.fromEvent(closeButton1, 'click');
// and the same logic for close2 and close3

var requestStream = refreshClickStream.startWith('startup click')
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  });

var responseStream = requestStream
  .flatMap(function (requestUrl) {
    return Rx.Observable.fromPromise($.ajax({url: requestUrl}));
  });

var suggestion1Stream = close1ClickStream.startWith('startup click')
  .combineLatest(responseStream,             
    function(click, listUsers) {
      return listUsers[Math.floor(Math.random()*listUsers.length)];
    }
  )
  .merge(
    refreshClickStream.map(function(){ return null; })
  )
  .startWith(null);
// and the same logic for suggestion2Stream and suggestion3Stream

suggestion1Stream.subscribe(function(suggestion) {
  if (suggestion === null) {
    // hide the first suggestion DOM element
  }
  else {
    // show the first suggestion DOM element
    // and render the data
  }
});

Sample có thể vọc ở http://jsfiddle.net/staltz/8jFJH/48/

https://gist.github.com/staltz

Bài viết gốc được đăng tải tại VuiLapTrinh

Xem thêm các việc làm IT hấp dẫn tại Station D

Bài viết liên quan

Bộ cài đặt Laravel Installer đã hỗ trợ tích hợp Jetstream

Bộ cài đặt Laravel Installer đã hỗ trợ tích hợp Jetstream

Bài viết được sự cho phép của tác giả Chung Nguyễn Hôm nay, nhóm Laravel đã phát hành một phiên bản chính mới của “ laravel/installer ” bao gồm hỗ trợ khởi động nhanh các dự án Jetstream. Với phiên bản mới này khi bạn chạy laravel new project-name , bạn sẽ nhận được các tùy chọn Jetstream. Ví dụ: API Authentication trong Laravel-Vue SPA sử dụng Jwt-auth Cách sử dụng Laravel với Socket.IO laravel new foo --jet --dev Sau đó, nó sẽ hỏi bạn thích stack Jetstream nào hơn: Which Jetstream stack do you prefer? [0] Livewire [1] inertia > livewire Will your application use teams? (yes/no) [no]: ... Nếu bạn đã cài bộ Laravel Installer, để nâng cấp lên phiên bản mới bạn chạy lệnh: composer global update Một số trường hợp cập nhật bị thất bại, bạn hãy thử, gỡ đi và cài đặt lại nha composer global remove laravel/installer composer global require laravel/installer Bài viết gốc được đăng tải tại chungnguyen.xyz Có thể bạn quan tâm: Cài đặt Laravel Làm thế nào để chạy Sql Server Installation Center sau khi đã cài đặt xong Sql Server? Quản lý các Laravel route gọn hơn và dễ dàng hơn Xem thêm Tuyển dụng lập trình Laravel hấp dẫn trên Station D

By stationd
Principle thiết kế của các sản phẩm nổi tiếng

Principle thiết kế của các sản phẩm nổi tiếng

Tác giả: Lưu Bình An Phù hợp cho các bạn thiết kế nào ko muốn làm code dạo, design dạo nữa, bạn muốn cái gì đó cao hơn ở tầng khái niệm Nếu lập trình chúng ta có các nguyên tắc chung khi viết code như KISS , DRY , thì trong thiết kế cũng có những nguyên tắc chính khi làm việc. Những nguyên tắc này sẽ là kim chỉ nam, nếu có tranh cãi giữa các member trong team, thì cứ đè nguyên tắc này ra mà giải quyết (nghe hơi có mùi cứng nhắc, mình thì thích tùy cơ ứng biến hơn) Tìm các vị trí tuyển dụng designer lương cao cho bạn Nguyên tắc thiết kế của GOV.UK Đây là danh sách của trang GOV.UK Bắt đầu với thứ user cần Làm ít hơn Thiết kế với dữ liệu Làm mọi thứ thật dễ dàng Lặp. Rồi lặp lại lần nữa Dành cho tất cả mọi người Hiểu ngữ cảnh hiện tại Làm dịch vụ digital, không phải làm website Nhất quán, nhưng không hòa tan (phải có chất riêng với thằng khác) Cởi mở, mọi thứ tốt hơn Bao trừu tượng luôn các bạn, trang Gov.uk này cũng có câu tổng quát rất hay Thiết kế tốt là thiết kế có thể sử dụng. Phục vụ cho nhiều đối tượng sử dụng, dễ đọc nhất nhất có thể. Nếu phải từ bỏ đẹp tinh tế – thì cứ bỏ luôn . Chúng ta tạo sản phẩm cho nhu cầu sử dụng, không phải cho người hâm mộ . Chúng ta thiết kế để cả nước sử dụng, không phải những người đã từng sử dụng web. Những người cần dịch vụ của chúng ta nhất là những người đang cảm thấy khó sử dụng dịch...

By stationd
Hiểu về trình duyệt – How browsers work

Hiểu về trình duyệt – How browsers work

Bài viết được sự cho phép của vntesters.com Khi nhìn từ bên ngoài, trình duyệt web giống như một ứng dụng hiển thị những thông tin và tài nguyên từ server lên màn hình người sử dụng, nhưng để làm được công việc hiển thị đó đòi hỏi trình duyệt phải xử lý rất nhiều thông tin và nhiều tầng phía bên dưới. Việc chúng ta (Developers, Testers) tìm hiểu càng sâu tầng bên dưới để nắm được nguyên tắc hoạt động và xử lý của trình duyệt sẽ rất hữu ích trong công việc viết code, sử dụng các tài nguyên cũng như kiểm thử ứng dụng của mình. Cách để npm packages chạy trong browser Câu hỏi phỏng vấn mẹo về React: Component hay element được render trong browser? Khi hiểu được cách thức hoạt động của trình duyệt chúng ta có thể trả lời được rất nhiều câu hỏi như: Tại sao cùng một trang web lại hiển thị khác nhau trên hai trình duyệt? Tại sao chức năng này đang chạy tốt trên trình duyệt Firefox nhưng qua trình duyệt khác lại bị lỗi? Làm sao để trang web hiển thị nội dung nhanh và tối ưu hơn một chút?… Hy vọng sau bài này sẽ giúp các bạn có một cái nhìn rõ hơn cũng như giúp ích được trong công việc hiện tại. 1. Cấu trúc của một trình duyệt Trước tiên chúng ta đi qua cấu trúc, thành phần chung và cơ bản nhất của một trình duyệt web hiện đại, nó sẽ gồm các thành phần (tầng) như sau: Thành phần nằm phía trên là những thành phần gần với tương tác của người dùng, càng phía dưới thì càng sâu và nặng về xử lý dữ liệu hơn tương tác. Nhiệm...

By stationd
Thị trường EdTech Vietnam- Nhiều tiềm năng nhưng còn bị bỏ ngỏ tại Việt Nam

Thị trường EdTech Vietnam- Nhiều tiềm năng nhưng còn bị bỏ ngỏ tại Việt Nam

Lĩnh vực EdTech (ứng dụng công nghệ vào các sản phẩm giáo dục) trên toàn cầu hiện nay đã tương đối phong phú với nhiều tên tuổi lớn phân phối đều trên các hạng mục như Broad Online Learning Platforms (nền tảng cung cấp khóa học online đại chúng – tiêu biểu như Coursera, Udemy, KhanAcademy,…) Learning Management Systems (hệ thống quản lý lớp học – tiêu biểu như Schoology, Edmodo, ClassDojo,…) Next-Gen Study Tools (công cụ hỗ trợ học tập – tiểu biểu như Kahoot!, Lumosity, Curriculet,…) Tech Learning (đào tạo công nghệ – tiêu biểu như Udacity, Codecademy, PluralSight,…), Enterprise Learning (đào tạo trong doanh nghiệp – tiêu biểu như Edcast, ExecOnline, Grovo,..),… Hiện nay thị trường EdTech tại Việt Nam đã đón nhận khoảng đầu tư khoảng 55 triệu đô cho lĩnh vực này nhiều đơn vị nước ngoài đang quan tâm mạnh đến thị trường này ngày càng nhiều hơn. Là một trong những xu hướng phát triển tốt, và có doanh nghiệp đã hoạt động khá lâu trong ngành nêu tại infographic như Topica, nhưng EdTech vẫn chỉ đang trong giai đoạn sơ khai tại Việt Nam. Tại Việt Nam, hệ sinh thái EdTech trong nước vẫn còn rất non trẻ và thiếu vắng nhiều tên tuổi trong các hạng mục như Enterprise Learning (mới chỉ có MANA), School Administration (hệ thống quản lý trường học) hay Search (tìm kiếm, so sánh trường và khóa học),… Với chỉ dưới 5% số dân công sở có sử dụng một trong các dịch vụ giáo dục online, EdTech cho thấy vẫn còn một thị trường rộng lớn đang chờ được khai phá. *** Vừa qua Station D đã công bố Báo cáo Vietnam IT Landscape 2019 đem đến cái nhìn toàn cảnh về các ứng dụng công...

By stationd