<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:series="https://publishpress.com/"
	>

<channel>
	<title>VueJS Archives - Tomoshare</title>
	<atom:link href="https://blog.tomosia.com.vn/danh-muc/javascript/vuejs/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.tomosia.com.vn/danh-muc/javascript/vuejs/</link>
	<description>Kênh chia sẻ kiến thức Tomosia Việt Nam</description>
	<lastBuildDate>Tue, 05 Dec 2023 02:17:26 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://blog.tomosia.com.vn/wp-content/uploads/2023/09/cropped-icon-32x32.png</url>
	<title>VueJS Archives - Tomoshare</title>
	<link>https://blog.tomosia.com.vn/danh-muc/javascript/vuejs/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Vuex vs Pinia: So sánh các thư viện quản lý State Vue</title>
		<link>https://blog.tomosia.com.vn/vuex-vs-pinia-so-sanh-cac-thu-vien-quan-ly-state-vue/</link>
					<comments>https://blog.tomosia.com.vn/vuex-vs-pinia-so-sanh-cac-thu-vien-quan-ly-state-vue/#comments</comments>
		
		<dc:creator><![CDATA[Tien Ho]]></dc:creator>
		<pubDate>Tue, 05 Dec 2023 02:17:23 +0000</pubDate>
				<category><![CDATA[VueJS]]></category>
		<category><![CDATA[javascript]]></category>
		<guid isPermaLink="false">https://blog.tomosia.com.vn/?p=2243</guid>

					<description><![CDATA[<p>Vuex và Pinia là hai thư viện quản lý trạng thái cho các ứng dụng Vue.js.&#160;Vuex là thư&#8230;</p>
<p>The post <a href="https://blog.tomosia.com.vn/vuex-vs-pinia-so-sanh-cac-thu-vien-quan-ly-state-vue/">Vuex vs Pinia: So sánh các thư viện quản lý State Vue</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="576" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/12/state-1024x576.png" alt="" class="wp-image-2244" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/12/state-1024x576.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/state-300x169.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/state-768x432.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/state-380x214.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/state-800x450.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/state-1160x653.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/state.png 1280w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>Vuex và Pinia là hai thư viện quản lý trạng thái cho các ứng dụng Vue.js.&nbsp;Vuex là thư viện cũ hơn và lâu đời hơn, nhưng Pinia đang trở nên phổ biến nhờ API đơn giản hơn và hỗ trợ TypeScript tốt hơn.</p>



<h2 id="quan-ly-state-la-gi" class="wp-block-heading">Quản lý state là gì?</h2>



<p>Quản lý state là quá trình quản lý dữ liệu được chia sẻ giữa các thành phần khác nhau trong ứng dụng Vue.&nbsp;Điều này có thể phức tạp, đặc biệt đối với các ứng dụng phức tạp, nơi cần có các thư viện quản lý trạng thái như Vuex và Pinia.</p>



<h2 class="wp-block-heading has-large-font-size" id="ember40"><span id="vuex">Vuex</span></h2>



<p id="ember41">Vuex là một thư viện quản lý trạng thái (state management) cho ứng dụng Vue.js. Nó giữ trạng thái của ứng dụng trong một trung tâm duy nhất gọi là &#8220;store&#8221;. Vuex cũng cung cấp một số tính năng giúp quản lý trạng thái dễ dàng, chẳng hạn như:</p>



<ul class="wp-block-list">
<li><strong>Mutations:</strong> là các hàm thuần túy cập nhật trạng thái theo cách có thể dự đoán được.</li>



<li><strong>Actions:</strong> là các hàm không đồng bộ có thể được sử dụng để thực hiện các tác vụ cập nhật trạng thái, chẳng hạn như tìm nạp dữ liệu từ API.</li>



<li><strong>Getters:</strong> là các hàm thuần túy có thể được sử dụng để tính toán các giá trị từ trạng thái.</li>
</ul>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="880" height="585" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/12/image_state.png" alt="" class="wp-image-2246" style="width:680px;height:auto" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/12/image_state.png 880w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/image_state-300x199.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/image_state-768x511.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/image_state-380x253.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/image_state-800x532.png 800w" sizes="(max-width: 880px) 100vw, 880px" /></figure>



<h2 id="pinia" class="wp-block-heading">Pinia</h2>



<p id="ember46">Pinia cũng là một thư viện quản lý trạng thái, nhưng được thiết kế với mô hình dựa trên cơ sở TypeScript. Nó khuyến khích sử dụng các cửa hàng cụ thể cho từng phần của ứng dụng. Điều này có nghĩa là mỗi thành phần có thể có cửa hàng riêng, chứa trạng thái dành riêng cho thành phần đó.&nbsp;Pinia cũng cung cấp một số tính năng giúp quản lý trạng thái dễ dàng, chẳng hạn như:</p>



<ul class="wp-block-list">
<li><strong>Hỗ trợ Composition API:</strong> &nbsp;API của Pinia được thiết kế để sử dụng với Vue Composition API, giúp dễ dàng tạo và quản lý trạng thái theo cách mô-đun.</li>



<li><strong>Hỗ trợ TypeScript:</strong> &nbsp;Pinia có hỗ trợ TypeScript tích hợp, giúp bạn dễ dàng viết mã an toàn.</li>



<li><strong>Hỗ trợ Devtools:</strong> &nbsp;Pinia hỗ trợ Vue Devtools, giúp bạn dễ dàng gỡ lỗi và kiểm tra trạng thái của mình.</li>
</ul>



<figure class="wp-block-image size-full"><img decoding="async" width="1024" height="370" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/12/pinia.png" alt="" class="wp-image-2251" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/12/pinia.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/pinia-300x108.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/pinia-768x278.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/pinia-380x137.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/pinia-800x289.png 800w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 id="ban-nen-su-dung-pinia-hay-vuex" class="wp-block-heading">Bạn nên sử dụng Pinia hay Vuex?</h2>



<p>Mặc dù Pinia đã được công nhận là giải pháp quản lý trạng thái chính thức cho các ứng dụng Vue trong tương lai nhưng điều đó không có nghĩa là Vuex không còn được dùng nữa.&nbsp;Nếu bạn có một ứng dụng đang được sản xuất và sử dụng Vuex thì ứng dụng của bạn sẽ vẫn hoạt động tốt và bạn nên tiếp tục sử dụng Vuex vì việc di chuyển từ thư viện này sang thư viện khác có thể tốn rất nhiều công sức.</p>



<p>Tuy nhiên, nếu dự án của bạn vẫn đang trong giai đoạn phát triển ban đầu thì Pinia là lựa chọn phù hợp.&nbsp;Bạn sẽ được hưởng lợi từ cú pháp dễ hiểu và đơn giản hơn của Pinia cùng với các tính năng khác được đề cập trong bài viết này.</p>



<p><strong>Dưới đây là bảng tóm tắt những điểm khác biệt chính giữa Vuex và Pinia:</strong></p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="437" height="396" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/12/vuex-pinia.png" alt="" class="wp-image-2258" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/12/vuex-pinia.png 437w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/vuex-pinia-300x272.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/vuex-pinia-380x344.png 380w" sizes="auto, (max-width: 437px) 100vw, 437px" /></figure>



<h2 id="tai-sao-chon-pinia-thay-the-vuex" class="wp-block-heading" style="text-transform:capitalize">Tại sao chọn Pinia thay thế Vuex?</h2>



<p>Trong số các ưu điểm của thư viện Pinia, cá nhân mình thấy nó nổi trội nhất ở các điểm sau:</p>



<ul class="wp-block-list">
<li>Kích thước thư viện siêu nhẹ, chỉ khoảng 1kb</li>



<li>Hỗ trợ module hóa tốt</li>



<li>Hỗ trợ auto complete khi code tốt. Thay vì phải khai báo mapAction(…), mapGetter(…).v.v…</li>



<li>Dễ sử dụng, vì cách viết giống như bạn vẫn khai báo và sử dụng state trong từng component vậy.</li>
</ul>



<p>Và còn nhiều ưu điểm khác nữa. Mình nghĩ sau khi thực hành và ứng dụng vào dự án, bạn sẽ trải nghiệm và nhận ra các ưu điểm này.</p>



<p>Cảm ơn mọi người đã dành thời gian để đọc 🙏  </p>



<p>Have a nice day!</p>
<p>The post <a href="https://blog.tomosia.com.vn/vuex-vs-pinia-so-sanh-cac-thu-vien-quan-ly-state-vue/">Vuex vs Pinia: So sánh các thư viện quản lý State Vue</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.tomosia.com.vn/vuex-vs-pinia-so-sanh-cac-thu-vien-quan-ly-state-vue/feed/</wfw:commentRss>
			<slash:comments>5</slash:comments>
		
		
			</item>
		<item>
		<title>Refresh token trong Axios như thế nào?</title>
		<link>https://blog.tomosia.com.vn/refresh-token-trong-axios-nhu-the-nao/</link>
					<comments>https://blog.tomosia.com.vn/refresh-token-trong-axios-nhu-the-nao/#comments</comments>
		
		<dc:creator><![CDATA[Tien Ho]]></dc:creator>
		<pubDate>Mon, 04 Dec 2023 01:38:25 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[ReactJS]]></category>
		<category><![CDATA[VueJS]]></category>
		<category><![CDATA[javascript]]></category>
		<guid isPermaLink="false">https://blog.tomosia.com.vn/?p=2211</guid>

					<description><![CDATA[<p>Sau đây, mình sẽ chia sẻ 1 tính năng đó là refresh token mà mình đã thực hiện&#8230;</p>
<p>The post <a href="https://blog.tomosia.com.vn/refresh-token-trong-axios-nhu-the-nao/">Refresh token trong Axios như thế nào?</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Sau đây, mình sẽ chia sẻ 1 tính năng đó là refresh token mà mình đã thực hiện trong dự án của mình. Mình nghĩ sẽ có nhiều dự án sẽ cần đến, sẽ giúp đến bạn :)))) </p>



<p>Về việc triển khai&nbsp;<code>refresh token</code>&nbsp;có thể không còn xa lạ đối với nhiều Frontend Dev trong chúng ta. Sau khi đăng nhập thành công,&nbsp;<code>token</code> sẽ được trả lại từ API (Thông thường api sẽ trả về <code>access_token</code> và <code>refresh_token</code>). </p>



<p>Chúng ta sẽ lưu trữ nó lại ở localStorage, cookie, etc&#8230; để đính vào headers của mỗi request cho việc xác thực các request mà chúng ta gửi đi.</p>



<p>Khi&nbsp;<code>token</code>&nbsp;này hết hạn, chúng ta sẽ cần gửi một yêu cầu để lấy một&nbsp;<code>token</code>&nbsp;mới bằng việc gửi&nbsp;<code>token</code>&nbsp;hết hạn trước đó hoặc mã&nbsp;<code>refresh_token</code>&nbsp;ban đầu được trả về từ API. Việc này phụ thuộc vào thiết kế API đó.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="369" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/12/image-4-1024x369.png" alt="" class="wp-image-2212" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/12/image-4-1024x369.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/image-4-300x108.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/image-4-768x277.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/image-4-1536x554.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/image-4-2048x738.png 2048w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/image-4-380x137.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/image-4-800x288.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/image-4-1160x418.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/image-4.png 2058w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Trước khi biết khi nào token hết hạn, chúng ta cần đính&nbsp;<code>token</code>&nbsp;vào mỗi request gửi đi. Bạn có thể hình dung việc chúng ta đính token vào mỗi request như thế này, để đảm bảo chúng ta luôn gửi&nbsp;<code>token</code>&nbsp;đến API để xác thực và biết khi nào token sẽ hết hạn (Tránh tự ý kiểm tra token ở client, việc đó không thực sự an toàn).</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:16.859375px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">JavaScript</span><span role="button" tabindex="0" data-code="const axiosClient = axios.create({
  baseURL: process.env.PR_API_HOST,
  headers: {
    'Content-Type': 'application/json',
  },
});

// Add a request interceptor
axiosClient.interceptors.request.use(
  (config) =&gt; {
    const storage = useStorage();
    const accessToken = storage.getItem(StorageKeyEnum.AccessToken);
  
    if (accessToken &amp;&amp; config.headers) {
      config.headers.Authorization = `Bearer ${accessToken}`;
    }
    return config;
  },
  (error) =&gt; Promise.reject(error)
);" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">const</span><span style="color: #F6F6F4"> axiosClient </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> axios.</span><span style="color: #62E884">create</span><span style="color: #F6F6F4">({</span></span>
<span class="line"><span style="color: #F6F6F4">  baseURL</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> process.env.</span><span style="color: #BF9EEE">PR_API_HOST</span><span style="color: #F6F6F4">,</span></span>
<span class="line"><span style="color: #F6F6F4">  headers</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #DEE492">&#39;</span><span style="color: #E7EE98">Content-Type</span><span style="color: #DEE492">&#39;</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&#39;</span><span style="color: #E7EE98">application/json</span><span style="color: #DEE492">&#39;</span><span style="color: #F6F6F4">,</span></span>
<span class="line"><span style="color: #F6F6F4">  },</span></span>
<span class="line"><span style="color: #F6F6F4">});</span></span>
<span class="line"></span>
<span class="line"><span style="color: #7B7F8B">// Add a request interceptor</span></span>
<span class="line"><span style="color: #F6F6F4">axiosClient.interceptors.request.</span><span style="color: #62E884">use</span><span style="color: #F6F6F4">(</span></span>
<span class="line"><span style="color: #F6F6F4">  (</span><span style="color: #FFB86C; font-style: italic">config</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">const</span><span style="color: #F6F6F4"> storage </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">useStorage</span><span style="color: #F6F6F4">();</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">const</span><span style="color: #F6F6F4"> accessToken </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> storage.</span><span style="color: #62E884">getItem</span><span style="color: #F6F6F4">(StorageKeyEnum.AccessToken);</span></span>
<span class="line"><span style="color: #F6F6F4">  </span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> (accessToken </span><span style="color: #F286C4">&amp;&amp;</span><span style="color: #F6F6F4"> config.headers) {</span></span>
<span class="line"><span style="color: #F6F6F4">      config.headers.Authorization </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #E7EE98">`Bearer </span><span style="color: #F286C4">${</span><span style="color: #F6F6F4">accessToken</span><span style="color: #F286C4">}</span><span style="color: #E7EE98">`</span><span style="color: #F6F6F4">;</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> config;</span></span>
<span class="line"><span style="color: #F6F6F4">  },</span></span>
<span class="line"><span style="color: #F6F6F4">  (</span><span style="color: #FFB86C; font-style: italic">error</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">Promise</span><span style="color: #F6F6F4">.</span><span style="color: #62E884">reject</span><span style="color: #F6F6F4">(error)</span></span>
<span class="line"><span style="color: #F6F6F4">);</span></span></code></pre></div>



<p>Khi mỗi request cần xác thực được gửi đi với&nbsp;<code>token</code>&nbsp;kèm theo, chúng ta sẽ biết&nbsp;<code>token</code>&nbsp;đó còn hợp lệ hay không bằng việc API sẽ giúp chúng ta xác minh token đó. khi&nbsp;<code>token</code>&nbsp;hết hạn, thông thường chúng ta sẽ nhận được mã phản hồi là&nbsp;<strong>401</strong>&nbsp;hoặc&nbsp;<strong>403</strong>.</p>



<p>Khi đó, chúng ta sẽ kiểm tra trên mỗi response nhận về. Nếu nó rơi vào các mã lỗi trên, thực hiện kịch bản&nbsp;<code>refresh token</code>&nbsp;mà chúng ta muốn. Ở bên dưới đoạn code này, hiện tại dự án của mình khi hết hạn token trả về mã lỗi là <strong>401</strong> và kèm theo 1 message <code>Signature has expired </code>nên mình đang thực hiện check theo điều kiện như vậy nhé =))) </p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:16.859375px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">JavaScript</span><span role="button" tabindex="0" data-code="axiosClient.interceptors.response.use(
  (response) =&gt; response.data,
  async (error) =&gt; {
    let message = '';
    const originalConfig = error.config;
    const code = error.response &amp;&amp; Number(error.response.status);
    switch (code) {
      case 401:
        if (error.response.data.error === RESPONSE_ERROR_MESSAGE.SIGNATURE_HAS_EXPIRED) {
          // Chúng ta sẽ refresh token tại đây
        break;
      case ...:
        break;
      default:
        message = ErrorHttpMessageEnum.Default;
      
    }
    return message;
  },
);" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">axiosClient.interceptors.response.</span><span style="color: #62E884">use</span><span style="color: #F6F6F4">(</span></span>
<span class="line"><span style="color: #F6F6F4">  (</span><span style="color: #FFB86C; font-style: italic">response</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> response.data,</span></span>
<span class="line"><span style="color: #F6F6F4">  </span><span style="color: #F286C4">async</span><span style="color: #F6F6F4"> (</span><span style="color: #FFB86C; font-style: italic">error</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> message </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&#39;&#39;</span><span style="color: #F6F6F4">;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">const</span><span style="color: #F6F6F4"> originalConfig </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> error.config;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">const</span><span style="color: #F6F6F4"> code </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> error.response </span><span style="color: #F286C4">&amp;&amp;</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">Number</span><span style="color: #F6F6F4">(error.response.status);</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">switch</span><span style="color: #F6F6F4"> (code) {</span></span>
<span class="line"><span style="color: #F6F6F4">      </span><span style="color: #F286C4">case</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">401</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> (error.response.data.error </span><span style="color: #F286C4">===</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">RESPONSE_ERROR_MESSAGE</span><span style="color: #F6F6F4">.</span><span style="color: #BF9EEE">SIGNATURE_HAS_EXPIRED</span><span style="color: #F6F6F4">) {</span></span>
<span class="line"><span style="color: #F6F6F4">          </span><span style="color: #7B7F8B">// Chúng ta sẽ refresh token tại đây</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">break</span><span style="color: #F6F6F4">;</span></span>
<span class="line"><span style="color: #F6F6F4">      </span><span style="color: #F286C4">case</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">...</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">break</span><span style="color: #F6F6F4">;</span></span>
<span class="line"><span style="color: #F6F6F4">      </span><span style="color: #F286C4">default</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">        message </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> ErrorHttpMessageEnum.Default;</span></span>
<span class="line"><span style="color: #F6F6F4">      </span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> message;</span></span>
<span class="line"><span style="color: #F6F6F4">  },</span></span>
<span class="line"><span style="color: #F6F6F4">);</span></span></code></pre></div>



<p>Tiếp theo, để đảm bảo chỉ một request refresh token được tạo ra khi nhiều request với token hết hạn phát sinh khi tải trang, chúng ta sẽ cần sử dụng Promise để delay và chỉ tạo duy nhất một yêu cầu cuối cùng. Kèm theo đó, là kiểm tra xem có request refresh token nào đã được gọi thông qua biến trạng thái<code> isRefreshing</code></p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:16.859375px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">JavaScript</span><span role="button" tabindex="0" data-code="// refresh token is false, to call refresh token api
if (!isRefreshing) {
// update status isRefreshing to be true
  isRefreshing = true;
  // this method fetches the new token
  refreshTokensLoginApi().then(() =&gt; {
    const storage = useStorage();
    const accessToken = storage.getItem(StorageKeyEnum.AccessToken);
    onRefreshed(accessToken);
    // after that, to clear all setup
    refreshSubscribers = [];
    isRefreshing = false;
  });
}

// setup callback to change token in headers authorization
const retryOrigReq = new Promise((resolve) =&gt; {
  subscribeTokenRefresh((token: string) =&gt; {
    // replace the expired token and retry
    originalConfig.headers.Authorization = `Bearer ${token}`;
    resolve(axiosClient(originalConfig));
  });
});

return retryOrigReq;

const subscribeTokenRefresh = (cb: (token: string) =&gt; void) =&gt; {
  refreshSubscribers.push(cb);
};

const onRefreshed = (token: string) =&gt; {
  refreshSubscribers.map((cb) =&gt; cb(token));
};" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #7B7F8B">// refresh token is false, to call refresh token api</span></span>
<span class="line"><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> (</span><span style="color: #F286C4">!</span><span style="color: #F6F6F4">isRefreshing) {</span></span>
<span class="line"><span style="color: #7B7F8B">// update status isRefreshing to be true</span></span>
<span class="line"><span style="color: #F6F6F4">  isRefreshing </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">true</span><span style="color: #F6F6F4">;</span></span>
<span class="line"><span style="color: #F6F6F4">  </span><span style="color: #7B7F8B">// this method fetches the new token</span></span>
<span class="line"><span style="color: #F6F6F4">  </span><span style="color: #62E884">refreshTokensLoginApi</span><span style="color: #F6F6F4">().</span><span style="color: #62E884">then</span><span style="color: #F6F6F4">(() </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">const</span><span style="color: #F6F6F4"> storage </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">useStorage</span><span style="color: #F6F6F4">();</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">const</span><span style="color: #F6F6F4"> accessToken </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> storage.</span><span style="color: #62E884">getItem</span><span style="color: #F6F6F4">(StorageKeyEnum.AccessToken);</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #62E884">onRefreshed</span><span style="color: #F6F6F4">(accessToken);</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #7B7F8B">// after that, to clear all setup</span></span>
<span class="line"><span style="color: #F6F6F4">    refreshSubscribers </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> [];</span></span>
<span class="line"><span style="color: #F6F6F4">    isRefreshing </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">false</span><span style="color: #F6F6F4">;</span></span>
<span class="line"><span style="color: #F6F6F4">  });</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #7B7F8B">// setup callback to change token in headers authorization</span></span>
<span class="line"><span style="color: #F286C4">const</span><span style="color: #F6F6F4"> retryOrigReq </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4; font-weight: bold">new</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">Promise</span><span style="color: #F6F6F4">((</span><span style="color: #FFB86C; font-style: italic">resolve</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">  </span><span style="color: #62E884">subscribeTokenRefresh</span><span style="color: #F6F6F4">((</span><span style="color: #FFB86C; font-style: italic">token</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">string</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #7B7F8B">// replace the expired token and retry</span></span>
<span class="line"><span style="color: #F6F6F4">    originalConfig.headers.Authorization </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #E7EE98">`Bearer </span><span style="color: #F286C4">${</span><span style="color: #F6F6F4">token</span><span style="color: #F286C4">}</span><span style="color: #E7EE98">`</span><span style="color: #F6F6F4">;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #62E884">resolve</span><span style="color: #F6F6F4">(</span><span style="color: #62E884">axiosClient</span><span style="color: #F6F6F4">(originalConfig));</span></span>
<span class="line"><span style="color: #F6F6F4">  });</span></span>
<span class="line"><span style="color: #F6F6F4">});</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> retryOrigReq;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F286C4">const</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">subscribeTokenRefresh</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> (</span><span style="color: #62E884">cb</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> (</span><span style="color: #FFB86C; font-style: italic">token</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">string</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">void</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">  refreshSubscribers.</span><span style="color: #62E884">push</span><span style="color: #F6F6F4">(cb);</span></span>
<span class="line"><span style="color: #F6F6F4">};</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F286C4">const</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">onRefreshed</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> (</span><span style="color: #FFB86C; font-style: italic">token</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">string</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">  refreshSubscribers.</span><span style="color: #62E884">map</span><span style="color: #F6F6F4">((</span><span style="color: #FFB86C; font-style: italic">cb</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">cb</span><span style="color: #F6F6F4">(token));</span></span>
<span class="line"><span style="color: #F6F6F4">};</span></span></code></pre></div>



<p>Ở đây,&nbsp;<code>useStorage()</code>&nbsp;là mình đang viết 1 hook để tương tác <code>get, set, remove token</code> với các storage.&nbsp;Mục đích mình viết hooks để mình tái sử dụng lại cho được nhiều components trong dự án đó, thay vì mình phải viết lại từng nơi. Nó giúp code trong dự án mình tường minh, dễ quản lý hơn, tránh lặp lại code.</p>



<p>Tóm gọn lại, chúng ta sẽ có một hàm&nbsp;<code>refresh token&nbsp;</code>như bên dưới.</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:16.859375px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">JavaScript</span><span role="button" tabindex="0" data-code="import axios from 'axios';
import {
  ErrorHttpMessageEnum, StorageKeyEnum, RESPONSE_ERROR_MESSAGE
} from 'src/constant';
import { refreshTokensLoginApi, clearTokenRedirectLogin } from 'src/helpers/auth';
import { useStorage } from 'src/hooks/useStorage';

let isRefreshing = false;
let refreshSubscribers: ((token: string) =&gt; void)[] = [];

const axiosClient = axios.create({
  baseURL: process.env.PR_API_HOST,
  headers: {
    'Content-Type': 'application/json',
  },
});

// Add a request interceptor
axiosClient.interceptors.request.use(
  (config) =&gt; {
    const storage = useStorage();
    const accessToken = storage.getItem(StorageKeyEnum.AccessToken);
    
    if (accessToken &amp;&amp; config.headers) {
      config.headers.Authorization = `Bearer ${accessToken}`;
    }
    return config;
  },
  (error) =&gt; Promise.reject(error)
);

// Add a response interceptor
axiosClient.interceptors.response.use(
  (response) =&gt; response.data,
  async (error) =&gt; {
    let message = '';
    const originalConfig = error.config;
    const code = error.response &amp;&amp; Number(error.response.status);
    
    switch (code) {
      case 401:
        if (error.response.data.error === RESPONSE_ERROR_MESSAGE.SIGNATURE_HAS_EXPIRED) {
          if (!isRefreshing) {
            isRefreshing = true;
            refreshTokensLoginApi().then(() =&gt; {
              const storage = useStorage();
              const accessToken = storage.getItem(StorageKeyEnum.AccessToken);
              onRefreshed(accessToken);
              
              isRefreshing = false;
              refreshSubscribers = [];
            });
          }

          const retryOrigReq = new Promise((resolve) =&gt; {
            subscribeTokenRefresh((token: string) =&gt; {
              // replace the expired token and retry
              originalConfig.headers.Authorization = `Bearer ${token}`;
              resolve(axiosClient(originalConfig));
            });
          });

          return retryOrigReq;
        }
        clearTokenRedirectLogin();

        message = ErrorHttpMessageEnum.Code401;
        break;
      case ...:
        break;
      default:
        message = ErrorHttpMessageEnum.Default;
    }
    return message;
  },
);

const subscribeTokenRefresh = (cb: (token: string) =&gt; void) =&gt; {
  refreshSubscribers.push(cb);
};

const onRefreshed = (token: string) =&gt; {
  refreshSubscribers.map((cb) =&gt; cb(token));
};

export default axiosClient;
" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">import</span><span style="color: #F6F6F4"> axios </span><span style="color: #F286C4">from</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&#39;</span><span style="color: #E7EE98">axios</span><span style="color: #DEE492">&#39;</span><span style="color: #F6F6F4">;</span></span>
<span class="line"><span style="color: #F286C4">import</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">  ErrorHttpMessageEnum, StorageKeyEnum, RESPONSE_ERROR_MESSAGE</span></span>
<span class="line"><span style="color: #F6F6F4">} </span><span style="color: #F286C4">from</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&#39;</span><span style="color: #E7EE98">src/constant</span><span style="color: #DEE492">&#39;</span><span style="color: #F6F6F4">;</span></span>
<span class="line"><span style="color: #F286C4">import</span><span style="color: #F6F6F4"> { refreshTokensLoginApi, clearTokenRedirectLogin } </span><span style="color: #F286C4">from</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&#39;</span><span style="color: #E7EE98">src/helpers/auth</span><span style="color: #DEE492">&#39;</span><span style="color: #F6F6F4">;</span></span>
<span class="line"><span style="color: #F286C4">import</span><span style="color: #F6F6F4"> { useStorage } </span><span style="color: #F286C4">from</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&#39;</span><span style="color: #E7EE98">src/hooks/useStorage</span><span style="color: #DEE492">&#39;</span><span style="color: #F6F6F4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> isRefreshing </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">false</span><span style="color: #F6F6F4">;</span></span>
<span class="line"><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> refreshSubscribers</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> ((</span><span style="color: #FFB86C; font-style: italic">token</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">string</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">void</span><span style="color: #F6F6F4">)[] </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> [];</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F286C4">const</span><span style="color: #F6F6F4"> axiosClient </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> axios.</span><span style="color: #62E884">create</span><span style="color: #F6F6F4">({</span></span>
<span class="line"><span style="color: #F6F6F4">  baseURL</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> process.env.</span><span style="color: #BF9EEE">PR_API_HOST</span><span style="color: #F6F6F4">,</span></span>
<span class="line"><span style="color: #F6F6F4">  headers</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #DEE492">&#39;</span><span style="color: #E7EE98">Content-Type</span><span style="color: #DEE492">&#39;</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&#39;</span><span style="color: #E7EE98">application/json</span><span style="color: #DEE492">&#39;</span><span style="color: #F6F6F4">,</span></span>
<span class="line"><span style="color: #F6F6F4">  },</span></span>
<span class="line"><span style="color: #F6F6F4">});</span></span>
<span class="line"></span>
<span class="line"><span style="color: #7B7F8B">// Add a request interceptor</span></span>
<span class="line"><span style="color: #F6F6F4">axiosClient.interceptors.request.</span><span style="color: #62E884">use</span><span style="color: #F6F6F4">(</span></span>
<span class="line"><span style="color: #F6F6F4">  (</span><span style="color: #FFB86C; font-style: italic">config</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">const</span><span style="color: #F6F6F4"> storage </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">useStorage</span><span style="color: #F6F6F4">();</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">const</span><span style="color: #F6F6F4"> accessToken </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> storage.</span><span style="color: #62E884">getItem</span><span style="color: #F6F6F4">(StorageKeyEnum.AccessToken);</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> (accessToken </span><span style="color: #F286C4">&amp;&amp;</span><span style="color: #F6F6F4"> config.headers) {</span></span>
<span class="line"><span style="color: #F6F6F4">      config.headers.Authorization </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #E7EE98">`Bearer </span><span style="color: #F286C4">${</span><span style="color: #F6F6F4">accessToken</span><span style="color: #F286C4">}</span><span style="color: #E7EE98">`</span><span style="color: #F6F6F4">;</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> config;</span></span>
<span class="line"><span style="color: #F6F6F4">  },</span></span>
<span class="line"><span style="color: #F6F6F4">  (</span><span style="color: #FFB86C; font-style: italic">error</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">Promise</span><span style="color: #F6F6F4">.</span><span style="color: #62E884">reject</span><span style="color: #F6F6F4">(error)</span></span>
<span class="line"><span style="color: #F6F6F4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #7B7F8B">// Add a response interceptor</span></span>
<span class="line"><span style="color: #F6F6F4">axiosClient.interceptors.response.</span><span style="color: #62E884">use</span><span style="color: #F6F6F4">(</span></span>
<span class="line"><span style="color: #F6F6F4">  (</span><span style="color: #FFB86C; font-style: italic">response</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> response.data,</span></span>
<span class="line"><span style="color: #F6F6F4">  </span><span style="color: #F286C4">async</span><span style="color: #F6F6F4"> (</span><span style="color: #FFB86C; font-style: italic">error</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> message </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&#39;&#39;</span><span style="color: #F6F6F4">;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">const</span><span style="color: #F6F6F4"> originalConfig </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> error.config;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">const</span><span style="color: #F6F6F4"> code </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> error.response </span><span style="color: #F286C4">&amp;&amp;</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">Number</span><span style="color: #F6F6F4">(error.response.status);</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">switch</span><span style="color: #F6F6F4"> (code) {</span></span>
<span class="line"><span style="color: #F6F6F4">      </span><span style="color: #F286C4">case</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">401</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> (error.response.data.error </span><span style="color: #F286C4">===</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">RESPONSE_ERROR_MESSAGE</span><span style="color: #F6F6F4">.</span><span style="color: #BF9EEE">SIGNATURE_HAS_EXPIRED</span><span style="color: #F6F6F4">) {</span></span>
<span class="line"><span style="color: #F6F6F4">          </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> (</span><span style="color: #F286C4">!</span><span style="color: #F6F6F4">isRefreshing) {</span></span>
<span class="line"><span style="color: #F6F6F4">            isRefreshing </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">true</span><span style="color: #F6F6F4">;</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #62E884">refreshTokensLoginApi</span><span style="color: #F6F6F4">().</span><span style="color: #62E884">then</span><span style="color: #F6F6F4">(() </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">              </span><span style="color: #F286C4">const</span><span style="color: #F6F6F4"> storage </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">useStorage</span><span style="color: #F6F6F4">();</span></span>
<span class="line"><span style="color: #F6F6F4">              </span><span style="color: #F286C4">const</span><span style="color: #F6F6F4"> accessToken </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> storage.</span><span style="color: #62E884">getItem</span><span style="color: #F6F6F4">(StorageKeyEnum.AccessToken);</span></span>
<span class="line"><span style="color: #F6F6F4">              </span><span style="color: #62E884">onRefreshed</span><span style="color: #F6F6F4">(accessToken);</span></span>
<span class="line"><span style="color: #F6F6F4">              </span></span>
<span class="line"><span style="color: #F6F6F4">              isRefreshing </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">false</span><span style="color: #F6F6F4">;</span></span>
<span class="line"><span style="color: #F6F6F4">              refreshSubscribers </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> [];</span></span>
<span class="line"><span style="color: #F6F6F4">            });</span></span>
<span class="line"><span style="color: #F6F6F4">          }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">          </span><span style="color: #F286C4">const</span><span style="color: #F6F6F4"> retryOrigReq </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4; font-weight: bold">new</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">Promise</span><span style="color: #F6F6F4">((</span><span style="color: #FFB86C; font-style: italic">resolve</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #62E884">subscribeTokenRefresh</span><span style="color: #F6F6F4">((</span><span style="color: #FFB86C; font-style: italic">token</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">string</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">              </span><span style="color: #7B7F8B">// replace the expired token and retry</span></span>
<span class="line"><span style="color: #F6F6F4">              originalConfig.headers.Authorization </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #E7EE98">`Bearer </span><span style="color: #F286C4">${</span><span style="color: #F6F6F4">token</span><span style="color: #F286C4">}</span><span style="color: #E7EE98">`</span><span style="color: #F6F6F4">;</span></span>
<span class="line"><span style="color: #F6F6F4">              </span><span style="color: #62E884">resolve</span><span style="color: #F6F6F4">(</span><span style="color: #62E884">axiosClient</span><span style="color: #F6F6F4">(originalConfig));</span></span>
<span class="line"><span style="color: #F6F6F4">            });</span></span>
<span class="line"><span style="color: #F6F6F4">          });</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">          </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> retryOrigReq;</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #62E884">clearTokenRedirectLogin</span><span style="color: #F6F6F4">();</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">        message </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> ErrorHttpMessageEnum.Code401;</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">break</span><span style="color: #F6F6F4">;</span></span>
<span class="line"><span style="color: #F6F6F4">      </span><span style="color: #F286C4">case</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">...</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">break</span><span style="color: #F6F6F4">;</span></span>
<span class="line"><span style="color: #F6F6F4">      </span><span style="color: #F286C4">default</span><span style="color: #F6F6F4">:</span></span>
<span class="line"><span style="color: #F6F6F4">        message </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> ErrorHttpMessageEnum.Default;</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> message;</span></span>
<span class="line"><span style="color: #F6F6F4">  },</span></span>
<span class="line"><span style="color: #F6F6F4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F286C4">const</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">subscribeTokenRefresh</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> (</span><span style="color: #62E884">cb</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> (</span><span style="color: #FFB86C; font-style: italic">token</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">string</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">void</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">  refreshSubscribers.</span><span style="color: #62E884">push</span><span style="color: #F6F6F4">(cb);</span></span>
<span class="line"><span style="color: #F6F6F4">};</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F286C4">const</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">onRefreshed</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> (</span><span style="color: #FFB86C; font-style: italic">token</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">string</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">  refreshSubscribers.</span><span style="color: #62E884">map</span><span style="color: #F6F6F4">((</span><span style="color: #FFB86C; font-style: italic">cb</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">=&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">cb</span><span style="color: #F6F6F4">(token));</span></span>
<span class="line"><span style="color: #F6F6F4">};</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F286C4">export</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">default</span><span style="color: #F6F6F4"> axiosClient;</span></span>
<span class="line"></span></code></pre></div>



<p class="has-medium-font-size"><strong>Kết luận</strong></p>



<p>Có rất nhiều cách để chúng ta triển khai refresh token và phía trên là một trong số cách hiện tại của mình đang sử dụng để&nbsp;<code>refresh token</code> trong dự án. Cảm ơn mọi người đã dành thời gian để đọc 🙏</p>



<p>Have a nice day!</p>
<p>The post <a href="https://blog.tomosia.com.vn/refresh-token-trong-axios-nhu-the-nao/">Refresh token trong Axios như thế nào?</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.tomosia.com.vn/refresh-token-trong-axios-nhu-the-nao/feed/</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
			</item>
		<item>
		<title>Tạo custom input trong vuejs</title>
		<link>https://blog.tomosia.com.vn/tao-custom-input-trong-vuejs/</link>
					<comments>https://blog.tomosia.com.vn/tao-custom-input-trong-vuejs/#comments</comments>
		
		<dc:creator><![CDATA[admin_tomosia]]></dc:creator>
		<pubDate>Thu, 05 Oct 2023 06:25:11 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[VueJS]]></category>
		<guid isPermaLink="false">https://blog.tomosia.com.vn/?p=342</guid>

					<description><![CDATA[<p>Có lẽ chúng ta đã quá quen thuộc với&#160;VueJS&#160;– một web framework vô cùng mạnh mẽ và “đẹp&#8230;</p>
<p>The post <a href="https://blog.tomosia.com.vn/tao-custom-input-trong-vuejs/">Tạo custom input trong vuejs</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Có lẽ chúng ta đã quá quen thuộc với&nbsp;<a rel="noreferrer noopener" href="https://vuejs.org/" target="_blank">VueJS</a>&nbsp;– một web framework vô cùng mạnh mẽ và “đẹp troai”.</p>



<p>Trong đó, một trong những tính năng không thể thiếu đó là 2-way data binding, cụ thể là sử dụng v-model.</p>



<p>Nếu chưa từng làm việc với vuejs thì mình sẽ giải thích một cách khái quát giúp bạn hiểu về v-model. Đây là một cách thức mà bạn hay dùng cho các form nhập liệu. Khi mà giá trị của các biến vừa dùng để hiển thị trên input, đồng thời khi người dùng nhập thì nó cũng tự động lưu vào biến đó. Vẫn hơi khó hiểu đúng không, vậy dưới đây là một ví dụ về v-model:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="&lt;template&gt;
  &lt;input type=&quot;text&quot; v-model=&quot;value&quot;/&gt;
  &lt;div&gt;giá trị nhập từ input: &lt;span style=&quot;color: red&quot;&gt;{{value}}&lt;/span&gt;&lt;/div&gt;
&lt;/template&gt;
&lt;script&gt;
export default {
  data() {
    return {
      value: ''
    }
  }
}
&lt;/script&gt;

// Input: Đây là giá trị được nhập từ input
// Output: giá trị nhập từ input: Đây là giá trị được nhập từ input" style="color:#F8F8F2;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F8F8F2">&lt;</span><span style="color: #FF79C6">template</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">  &lt;</span><span style="color: #FF79C6">input</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B; font-style: italic">type</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">text</span><span style="color: #E9F284">&quot;</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B; font-style: italic">v-model</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">value</span><span style="color: #E9F284">&quot;</span><span style="color: #F8F8F2">/&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">  &lt;</span><span style="color: #FF79C6">div</span><span style="color: #F8F8F2">&gt;giá trị nhập từ input: &lt;</span><span style="color: #FF79C6">span</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B; font-style: italic">style</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">color: red</span><span style="color: #E9F284">&quot;</span><span style="color: #F8F8F2">&gt;</span><span style="color: #FF79C6">{</span><span style="color: #F8F8F2">{value}</span><span style="color: #FF79C6">}</span><span style="color: #F8F8F2">&lt;/</span><span style="color: #FF79C6">span</span><span style="color: #F8F8F2">&gt;&lt;/</span><span style="color: #FF79C6">div</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;/</span><span style="color: #FF79C6">template</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;</span><span style="color: #FF79C6">script</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">export default </span><span style="color: #FF79C6">{</span></span>
<span class="line"><span style="color: #F8F8F2">  </span><span style="color: #50FA7B">data</span><span style="color: #F8F8F2">() {</span></span>
<span class="line"><span style="color: #F8F8F2">    return {</span></span>
<span class="line"><span style="color: #F8F8F2">      value: </span><span style="color: #E9F284">&#39;&#39;</span></span>
<span class="line"><span style="color: #F8F8F2">    }</span></span>
<span class="line"><span style="color: #F8F8F2">  }</span></span>
<span class="line"><span style="color: #FF79C6">}</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;/</span><span style="color: #FF79C6">script</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">// Input: Đây là giá trị được nhập từ input</span></span>
<span class="line"><span style="color: #6272A4">// Output: giá trị nhập từ input: Đây là giá trị được nhập từ input</span></span></code></pre></div>



<p>Dễ đúng không. Nhưng tùy vào dự án mà bạn cần tạo các custom input component mà trong đó cần phải xử lý 2-way data binding. Dưới đây là một số cách mà bạn có thể tự implement v-model:</p>



<ul class="wp-block-list">
<li>Watcher một biến local</li>



<li>Custom method để implement v-model</li>



<li>Sử dụng thuộc tính computer</li>



<li>&nbsp;Custom props và event</li>
</ul>



<h2 id="1-watcher-mot-bien-local" class="wp-block-heading">1. Watcher một biến local</h2>



<p>Đây có lẽ là cách mọi người hay nghĩ tới nhất khi cần implement v-model cho component.</p>



<p>Cách thực hiện đơn giản nhất là gồm những bước sau:</p>



<ul class="wp-block-list">
<li>chúng ta khai báo một props,</li>



<li>sau đó tạo một biến observable trong&nbsp;<code>data()</code>&nbsp;và khởi tạo giá trị là giá trị của props đã khai báo trước đó</li>



<li>Cuối cùng là sử dụng event để thông báo cho component cha, mục đích là để update giá trị props từ bên ngoài component.</li>
</ul>



<p>Nói thì có vẻ trừu tượng vậy thôi, nhưng nhìn code là bạn sẽ thấy nó đơn giản như nào.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="&lt;!-- Tạo component CustomInput.vue --&gt;
&lt;template&gt;
  &lt;input type=&quot;text&quot; v-model=&quot;model&quot; /&gt;
&lt;/template&gt;

&lt;script&gt;
export default {
  props: ['modelValue'],
  data() {
    return {
      model: this.modelValue
    }
  },
  watch: {
    model(currentValue) {
      this.$emit('update:modelValue', currentValue)
    }
  }
}
&lt;/script&gt;

&lt;!-- Cách sử dụng --&gt;
&lt;template&gt;
  &lt;CustomInput v-model=&quot;text&quot; /&gt;
  &lt;div&gt;giá trị nhập từ input: &lt;span style=&quot;color: red&quot;&gt;{{text}}&lt;/span&gt;&lt;/div&gt;
&lt;/template&gt;
&lt;script&gt;
import CustomInput from './components/CustomInput.vue'
export default {
  components: { CustomInput },
  data() {
    return {
      text: ''
    }
  }
}
&lt;/script&gt;" style="color:#F8F8F2;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #FF79C6">&lt;!--</span><span style="color: #F8F8F2"> Tạo component CustomInput.vue </span><span style="color: #FF79C6">--&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;</span><span style="color: #FF79C6">template</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">  &lt;</span><span style="color: #FF79C6">input</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B; font-style: italic">type</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">text</span><span style="color: #E9F284">&quot;</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B; font-style: italic">v-model</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">model</span><span style="color: #E9F284">&quot;</span><span style="color: #F8F8F2"> /&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;/</span><span style="color: #FF79C6">template</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">&lt;</span><span style="color: #FF79C6">script</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">export default </span><span style="color: #FF79C6">{</span></span>
<span class="line"><span style="color: #F8F8F2">  props: [</span><span style="color: #E9F284">&#39;</span><span style="color: #F1FA8C">modelValue</span><span style="color: #E9F284">&#39;</span><span style="color: #F8F8F2">],</span></span>
<span class="line"><span style="color: #F8F8F2">  </span><span style="color: #50FA7B">data</span><span style="color: #F8F8F2">() {</span></span>
<span class="line"><span style="color: #F8F8F2">    return {</span></span>
<span class="line"><span style="color: #F8F8F2">      model: </span><span style="color: #BD93F9; font-style: italic">this</span><span style="color: #F8F8F2">.modelValue</span></span>
<span class="line"><span style="color: #F8F8F2">    }</span></span>
<span class="line"><span style="color: #F8F8F2">  },</span></span>
<span class="line"><span style="color: #F8F8F2">  watch: {</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #50FA7B">model</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">currentValue</span><span style="color: #F8F8F2">) {</span></span>
<span class="line"><span style="color: #F8F8F2">      </span><span style="color: #BD93F9; font-style: italic">this</span><span style="color: #F8F8F2">.</span><span style="color: #50FA7B">$emit</span><span style="color: #F8F8F2">(</span><span style="color: #E9F284">&#39;</span><span style="color: #F1FA8C">update:modelValue</span><span style="color: #E9F284">&#39;</span><span style="color: #F8F8F2">, currentValue)</span></span>
<span class="line"><span style="color: #F8F8F2">    }</span></span>
<span class="line"><span style="color: #F8F8F2">  }</span></span>
<span class="line"><span style="color: #FF79C6">}</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;/</span><span style="color: #FF79C6">script</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">&lt;!--</span><span style="color: #F8F8F2"> Cách sử dụng </span><span style="color: #FF79C6">--&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;</span><span style="color: #FF79C6">template</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">  &lt;</span><span style="color: #8BE9FD; font-style: italic">CustomInput</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B; font-style: italic">v-model</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">text</span><span style="color: #E9F284">&quot;</span><span style="color: #F8F8F2"> /&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">  &lt;</span><span style="color: #FF79C6">div</span><span style="color: #F8F8F2">&gt;giá trị nhập từ input: &lt;</span><span style="color: #FF79C6">span</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B; font-style: italic">style</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">color: red</span><span style="color: #E9F284">&quot;</span><span style="color: #F8F8F2">&gt;</span><span style="color: #FF79C6">{</span><span style="color: #F8F8F2">{text}</span><span style="color: #FF79C6">}</span><span style="color: #F8F8F2">&lt;/</span><span style="color: #FF79C6">span</span><span style="color: #F8F8F2">&gt;&lt;/</span><span style="color: #FF79C6">div</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;/</span><span style="color: #FF79C6">template</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;</span><span style="color: #FF79C6">script</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">import CustomInput from &#39;./components/CustomInput.vue&#39;</span></span>
<span class="line"><span style="color: #F8F8F2">export default </span><span style="color: #FF79C6">{</span></span>
<span class="line"><span style="color: #F8F8F2">  components: { CustomInput },</span></span>
<span class="line"><span style="color: #F8F8F2">  </span><span style="color: #50FA7B">data</span><span style="color: #F8F8F2">() {</span></span>
<span class="line"><span style="color: #F8F8F2">    return {</span></span>
<span class="line"><span style="color: #F8F8F2">      text: </span><span style="color: #E9F284">&#39;&#39;</span></span>
<span class="line"><span style="color: #F8F8F2">    }</span></span>
<span class="line"><span style="color: #F8F8F2">  }</span></span>
<span class="line"><span style="color: #FF79C6">}</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;/</span><span style="color: #FF79C6">script</span><span style="color: #F8F8F2">&gt;</span></span></code></pre></div>



<h2 id="2-custom-method-de-implement-v-model" class="wp-block-heading">2. Custom method để implement v-model</h2>



<p>Nếu sử dụng watcher ảnh hưởng tới hiệu năng của ứng dụng. Vậy cách 2 này sẽ tận dụng event @input có sẵn của input native và tạo một custom method trong component của chúng ta.</p>



<p>Chúng ta sẽ truyền giá trị của input tới component cha thông qua emit một event, mục đích cũng là để có thể cập nhật props từ bên ngoài component.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="&lt;!-- Tạo component CustomInput.vue --&gt;
&lt;template&gt;
  &lt;input
    :value=&quot;modelValue&quot;
    @input=&quot;$emit('update:modelValue', $event.target.value)&quot;
  /&gt;
&lt;/template&gt;
&lt;script&gt;
export default {
  props: ['modelValue'],
  emits: ['update:modelValue']
}
&lt;/script&gt;

&lt;!-- Cách sử dụng --&gt;
&lt;template&gt;
  &lt;CustomInput v-model=&quot;text&quot; /&gt;
  &lt;div&gt;giá trị nhập từ input: &lt;span style=&quot;color: red&quot;&gt;{{ text }}&lt;/span&gt;&lt;/div&gt;
&lt;/template&gt;
&lt;script&gt;
import CustomInput from './components/CustomInput.vue'
export default {
  components: { CustomInput },
  data() {
    return {
      text: ''
    }
  }
}
&lt;/script&gt;" style="color:#F8F8F2;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #FF79C6">&lt;!--</span><span style="color: #F8F8F2"> Tạo component CustomInput.vue </span><span style="color: #FF79C6">--&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;</span><span style="color: #FF79C6">template</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">  &lt;</span><span style="color: #FF79C6">input</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF5555; font-style: italic; text-decoration: underline">:value=&quot;modelValue&quot;</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF5555; font-style: italic; text-decoration: underline">@input=&quot;$emit(&#39;update:modelValue&#39;,</span><span style="color: #F8F8F2"> </span><span style="color: #FF5555; font-style: italic; text-decoration: underline">$event.target.value)&quot;</span></span>
<span class="line"><span style="color: #F8F8F2">  /&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;/</span><span style="color: #FF79C6">template</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;</span><span style="color: #FF79C6">script</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">export default </span><span style="color: #FF79C6">{</span></span>
<span class="line"><span style="color: #F8F8F2">  props: [</span><span style="color: #E9F284">&#39;</span><span style="color: #F1FA8C">modelValue</span><span style="color: #E9F284">&#39;</span><span style="color: #F8F8F2">],</span></span>
<span class="line"><span style="color: #F8F8F2">  emits: [</span><span style="color: #E9F284">&#39;</span><span style="color: #F1FA8C">update:modelValue</span><span style="color: #E9F284">&#39;</span><span style="color: #F8F8F2">]</span></span>
<span class="line"><span style="color: #FF79C6">}</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;/</span><span style="color: #FF79C6">script</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">&lt;!--</span><span style="color: #F8F8F2"> Cách sử dụng </span><span style="color: #FF79C6">--&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;</span><span style="color: #FF79C6">template</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">  &lt;</span><span style="color: #8BE9FD; font-style: italic">CustomInput</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B; font-style: italic">v-model</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">text</span><span style="color: #E9F284">&quot;</span><span style="color: #F8F8F2"> /&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">  &lt;</span><span style="color: #FF79C6">div</span><span style="color: #F8F8F2">&gt;giá trị nhập từ input: &lt;</span><span style="color: #FF79C6">span</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B; font-style: italic">style</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">color: red</span><span style="color: #E9F284">&quot;</span><span style="color: #F8F8F2">&gt;</span><span style="color: #FF79C6">{</span><span style="color: #F8F8F2">{ text }</span><span style="color: #FF79C6">}</span><span style="color: #F8F8F2">&lt;/</span><span style="color: #FF79C6">span</span><span style="color: #F8F8F2">&gt;&lt;/</span><span style="color: #FF79C6">div</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;/</span><span style="color: #FF79C6">template</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;</span><span style="color: #FF79C6">script</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">import CustomInput from &#39;./components/CustomInput.vue&#39;</span></span>
<span class="line"><span style="color: #F8F8F2">export default </span><span style="color: #FF79C6">{</span></span>
<span class="line"><span style="color: #F8F8F2">  components: { CustomInput },</span></span>
<span class="line"><span style="color: #F8F8F2">  </span><span style="color: #50FA7B">data</span><span style="color: #F8F8F2">() {</span></span>
<span class="line"><span style="color: #F8F8F2">    return {</span></span>
<span class="line"><span style="color: #F8F8F2">      text: </span><span style="color: #E9F284">&#39;&#39;</span></span>
<span class="line"><span style="color: #F8F8F2">    }</span></span>
<span class="line"><span style="color: #F8F8F2">  }</span></span>
<span class="line"><span style="color: #FF79C6">}</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;/</span><span style="color: #FF79C6">script</span><span style="color: #F8F8F2">&gt;</span></span></code></pre></div>



<pre class="wp-block-verse">Lưu ý: Đối với Vue3, thuộc tính value được đổi tên thành modelValue và tên của event input được đổi thành update:modelValue</pre>



<h2 id="3-su-dung-thuoc-tinh-computer" class="wp-block-heading">3. Sử dụng thuộc tính computer</h2>



<p>Một cách khác nữa để implement v-model trong một custom component đó là sử dụng getter và setter của computed.</p>



<p>Bạn có thể định nghĩa một thuộc tính computed, sau đó:</p>



<ul class="wp-block-list">
<li>Implement một getter để trả về value của thuộc tính đó.</li>



<li>Một setter để emit một input event tới component cha</li>
</ul>



<p>Cụ thể cách viết code như sau:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="&lt;!-- Tạo component CustomInput.vue --&gt;
&lt;template&gt;
  &lt;input v-model=&quot;value&quot; /&gt;
&lt;/template&gt;&lt;script&gt;
export default {
  props: ['modelValue'],
  computed: {
    value: {
      get() {
        return this.modelValue
      },
      set(value) {
        this.$emit('update:modelValue', value)
      }
    }
  }
}
&lt;/script&gt;

&lt;!-- Cách sử dụng --&gt;
&lt;template&gt;
  &lt;CustomInput v-model=&quot;text&quot; /&gt;
  &lt;div&gt;giá trị nhập từ input: &lt;span style=&quot;color: red&quot;&gt;{{ text }}&lt;/span&gt;&lt;/div&gt;
&lt;/template&gt;
&lt;script&gt;
import CustomInput from './components/CustomInput.vue'
export default {
  components: { CustomInput },
  data() {
    return {
      text: ''
    }
  }
}
&lt;/script&gt;" style="color:#F8F8F2;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #FF79C6">&lt;!--</span><span style="color: #F8F8F2"> Tạo component CustomInput.vue </span><span style="color: #FF79C6">--&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;</span><span style="color: #FF79C6">template</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">  &lt;</span><span style="color: #FF79C6">input</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B; font-style: italic">v-model</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">value</span><span style="color: #E9F284">&quot;</span><span style="color: #F8F8F2"> /&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;/</span><span style="color: #FF79C6">template</span><span style="color: #F8F8F2">&gt;&lt;</span><span style="color: #FF79C6">script</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">export default </span><span style="color: #FF79C6">{</span></span>
<span class="line"><span style="color: #F8F8F2">  props: [</span><span style="color: #E9F284">&#39;</span><span style="color: #F1FA8C">modelValue</span><span style="color: #E9F284">&#39;</span><span style="color: #F8F8F2">],</span></span>
<span class="line"><span style="color: #F8F8F2">  computed: {</span></span>
<span class="line"><span style="color: #F8F8F2">    value</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2"> {</span></span>
<span class="line"><span style="color: #F8F8F2">      </span><span style="color: #50FA7B">get</span><span style="color: #F8F8F2">() {</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9; font-style: italic">this</span><span style="color: #F8F8F2">.modelValue</span></span>
<span class="line"><span style="color: #F8F8F2">      },</span></span>
<span class="line"><span style="color: #F8F8F2">      </span><span style="color: #50FA7B">set</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">value</span><span style="color: #F8F8F2">) {</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #BD93F9; font-style: italic">this</span><span style="color: #F8F8F2">.</span><span style="color: #50FA7B">$emit</span><span style="color: #F8F8F2">(</span><span style="color: #E9F284">&#39;</span><span style="color: #F1FA8C">update:modelValue</span><span style="color: #E9F284">&#39;</span><span style="color: #F8F8F2">, value)</span></span>
<span class="line"><span style="color: #F8F8F2">      }</span></span>
<span class="line"><span style="color: #F8F8F2">    }</span></span>
<span class="line"><span style="color: #F8F8F2">  }</span></span>
<span class="line"><span style="color: #FF79C6">}</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;/</span><span style="color: #FF79C6">script</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">&lt;!--</span><span style="color: #F8F8F2"> Cách sử dụng </span><span style="color: #FF79C6">--&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;</span><span style="color: #FF79C6">template</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">  &lt;</span><span style="color: #8BE9FD; font-style: italic">CustomInput</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B; font-style: italic">v-model</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">text</span><span style="color: #E9F284">&quot;</span><span style="color: #F8F8F2"> /&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">  &lt;</span><span style="color: #FF79C6">div</span><span style="color: #F8F8F2">&gt;giá trị nhập từ input: &lt;</span><span style="color: #FF79C6">span</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B; font-style: italic">style</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">color: red</span><span style="color: #E9F284">&quot;</span><span style="color: #F8F8F2">&gt;</span><span style="color: #FF79C6">{</span><span style="color: #F8F8F2">{ text }</span><span style="color: #FF79C6">}</span><span style="color: #F8F8F2">&lt;/</span><span style="color: #FF79C6">span</span><span style="color: #F8F8F2">&gt;&lt;/</span><span style="color: #FF79C6">div</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;/</span><span style="color: #FF79C6">template</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;</span><span style="color: #FF79C6">script</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">import CustomInput from &#39;./components/CustomInput.vue&#39;</span></span>
<span class="line"><span style="color: #F8F8F2">export default </span><span style="color: #FF79C6">{</span></span>
<span class="line"><span style="color: #F8F8F2">  components: { CustomInput },</span></span>
<span class="line"><span style="color: #F8F8F2">  </span><span style="color: #50FA7B">data</span><span style="color: #F8F8F2">() {</span></span>
<span class="line"><span style="color: #F8F8F2">    return {</span></span>
<span class="line"><span style="color: #F8F8F2">      text: </span><span style="color: #E9F284">&#39;&#39;</span></span>
<span class="line"><span style="color: #F8F8F2">    }</span></span>
<span class="line"><span style="color: #F8F8F2">  }</span></span>
<span class="line"><span style="color: #FF79C6">}</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;/</span><span style="color: #FF79C6">script</span><span style="color: #F8F8F2">&gt;</span></span></code></pre></div>



<h2 id="4-custom-props-va-event" class="wp-block-heading">4. Custom props và event</h2>



<p>Vì prop và event mặc định của v-model của Vue3 là <code>modelValue</code> và <code>update:modelValue </code>. Tuy nhiên chúng ta có thể biến đổi tên của nó bằng cách truyền đối số cho v-model</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="&lt;!-- Component cha --&gt;
&lt;CustomInput v-model:title=&quot;text&quot; /&gt;" style="color:#F8F8F2;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #FF79C6">&lt;!--</span><span style="color: #F8F8F2"> Component cha </span><span style="color: #FF79C6">--&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;</span><span style="color: #8BE9FD; font-style: italic">CustomInput</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B; font-style: italic">v-model</span><span style="color: #FF79C6">:</span><span style="color: #50FA7B; font-style: italic">title</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">text</span><span style="color: #E9F284">&quot;</span><span style="color: #F8F8F2"> /&gt;</span></span></code></pre></div>



<p>Trong trường hợp này, component con sẽ nhận một prop <code>title</code> và một emit một sự kiện <code>update:title</code> để cập nhật lại giá trị ở component cha</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="&lt;!-- CustomInput.vue --&gt;
&lt;template&gt;
  &lt;input
    type=&quot;text&quot;
    :value=&quot;title&quot;
    @input=&quot;$emit('update:title', $event.target.value)&quot;
  /&gt;
&lt;/template&gt;
&lt;script&gt;
export default {
  props: ['title'],
  emits: ['update:title']
}
&lt;/script&gt;" style="color:#F8F8F2;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #FF79C6">&lt;!--</span><span style="color: #F8F8F2"> CustomInput.vue </span><span style="color: #FF79C6">--&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;</span><span style="color: #FF79C6">template</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">  &lt;</span><span style="color: #FF79C6">input</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #50FA7B; font-style: italic">type</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">text</span><span style="color: #E9F284">&quot;</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF5555; font-style: italic; text-decoration: underline">:value=&quot;title&quot;</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF5555; font-style: italic; text-decoration: underline">@input=&quot;$emit(&#39;update:title&#39;,</span><span style="color: #F8F8F2"> </span><span style="color: #FF5555; font-style: italic; text-decoration: underline">$event.target.value)&quot;</span></span>
<span class="line"><span style="color: #F8F8F2">  /&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;/</span><span style="color: #FF79C6">template</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;</span><span style="color: #FF79C6">script</span><span style="color: #F8F8F2">&gt;</span></span>
<span class="line"><span style="color: #F8F8F2">export default </span><span style="color: #FF79C6">{</span></span>
<span class="line"><span style="color: #F8F8F2">  props: [</span><span style="color: #E9F284">&#39;</span><span style="color: #F1FA8C">title</span><span style="color: #E9F284">&#39;</span><span style="color: #F8F8F2">],</span></span>
<span class="line"><span style="color: #F8F8F2">  emits: [</span><span style="color: #E9F284">&#39;</span><span style="color: #F1FA8C">update:title</span><span style="color: #E9F284">&#39;</span><span style="color: #F8F8F2">]</span></span>
<span class="line"><span style="color: #FF79C6">}</span></span>
<span class="line"><span style="color: #F8F8F2">&lt;/</span><span style="color: #FF79C6">script</span><span style="color: #F8F8F2">&gt;</span></span></code></pre></div>



<h3 id="ket-luan" class="wp-block-heading">Kết luận</h3>



<p>Trên đây là một số cách để bạn implement v-model trong một component tùy chỉnh mà bạn tự tạo.Tương tự bạn cũng có thể làm với một số type khác của input như radio, checkbox,&#8230;</p>



<p>VueJS với ưu điểm nhanh, gọn, nhẹ và tùy biến cao, nên mình nghĩ ngoài những cách trên, sẽ còn nhiều cách khác nữa.</p>



<p>Bạn còn nghĩ ra cách nào khác để implement v-model cho component nữa không? Để lại ý kiến bình luận bên dưới nhé. Mình cũng đang đón chờ đây.</p>
<p>The post <a href="https://blog.tomosia.com.vn/tao-custom-input-trong-vuejs/">Tạo custom input trong vuejs</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.tomosia.com.vn/tao-custom-input-trong-vuejs/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>Sentry.io cho người mới bắt đầu (Vuejs)</title>
		<link>https://blog.tomosia.com.vn/sentry-io-cho-nguoi-moi-bat-dau-vuejs/</link>
					<comments>https://blog.tomosia.com.vn/sentry-io-cho-nguoi-moi-bat-dau-vuejs/#comments</comments>
		
		<dc:creator><![CDATA[admin_tomosia]]></dc:creator>
		<pubDate>Wed, 04 Oct 2023 07:08:05 +0000</pubDate>
				<category><![CDATA[VueJS]]></category>
		<category><![CDATA[sentry]]></category>
		<category><![CDATA[debug]]></category>
		<guid isPermaLink="false">https://blog.tomosia.com.vn/?p=259</guid>

					<description><![CDATA[<p>Tình huống Bạn đang phát triển một ứng dụng web Vue.js lớn với khách hàng cũng lớn không&#8230;</p>
<p>The post <a href="https://blog.tomosia.com.vn/sentry-io-cho-nguoi-moi-bat-dau-vuejs/">Sentry.io cho người mới bắt đầu (Vuejs)</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/sentry-and-vue-js-1024x375.png" alt="" class="wp-image-260" style="width:680px;height:249px" width="680" height="249" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/sentry-and-vue-js-1024x375.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/sentry-and-vue-js-300x110.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/sentry-and-vue-js-768x282.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/sentry-and-vue-js-380x139.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/sentry-and-vue-js-800x293.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/sentry-and-vue-js-1160x425.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/sentry-and-vue-js.png 1200w" sizes="auto, (max-width: 680px) 100vw, 680px" /></figure>



<h2 id="tinh-huong" class="wp-block-heading">Tình huống</h2>



<p>Bạn đang phát triển một ứng dụng web Vue.js lớn với khách hàng cũng lớn không kém. Một ngày đẹp trời nào đó, khách hàng gửi cho bạn 1 bug mà thoạt nhìn có vẻ đơn giản, tuy nhiên, bạn không thể tái hiện lỗi này trong môi trường phát triển của mình, và bạn không có thông tin rõ ràng về tình trạng lỗi. Trong tình huống này, bạn có thể sử dụng <a href="https://sentry.io/welcome/"><strong>Sentry</strong></a> để giúp xác định và giải quyết lỗi một cách hiệu quả. Vậy cùng mình đi tìm hiểu xem Sentry là gì mà lại lợi hại như vậy.</p>



<h2 id="sentry-la-gi" class="wp-block-heading">Sentry là gì</h2>



<p>Sentry là một dịch vụ quản lý lỗi (error tracking) và giám sát (monitoring) phần mềm được sử dụng để theo dõi và ghi lại các lỗi và sự cố trong ứng dụng phần mềm. Sentry giúp các nhà phát triển và nhóm phát triển phát hiện, theo dõi, và khắc phục lỗi một cách hiệu quả. Dịch vụ này có thể được tích hợp vào các ứng dụng web và ứng dụng di động để ghi lại thông tin về lỗi, crash, và sự cố xảy ra trong quá trình chạy ứng dụng</p>



<h2 id="loi-ich-cua-sentry" class="wp-block-heading">Lợi ích của Sentry</h2>



<ul class="wp-block-list">
<li><strong>Ghi lại lỗi tự động:</strong> Sentry có thể được tích hợp vào mã của bạn để tự động ghi lại thông tin về lỗi mỗi khi nó xảy ra trong môi trường sản xuất. Điều này cho phép bạn nhận được thông báo ngay khi có sự cố xảy ra.</li>



<li><strong>Thông tin về lỗi chi tiết:</strong> Sentry không chỉ ghi lại lỗi mà còn cung cấp thông tin chi tiết về nguồn gốc của lỗi, ngăn chặn lỗi, và cách lỗi xảy ra. Điều này giúp bạn dễ dàng xác định vị trí và nguyên nhân gây ra lỗi.</li>



<li><strong>Theo dõi thời gian thực:</strong> Sentry cung cấp một giao diện quản lý web cho phép bạn theo dõi lỗi trong thời gian thực và tìm hiểu về tần suất và ngữ cảnh xảy ra lỗi.</li>



<li><strong>Cung cấp stack trace:</strong> Sentry có khả năng tạo ra stack trace, cho phép bạn xem các hàm và dòng mã cụ thể liên quan đến lỗi. Điều này giúp bạn tìm ra lỗi trong mã của bạn.</li>



<li><strong>Thông báo ngay lập tức: </strong>Sentry có thể bắn thông báo qua mail, SMS, &#8230;</li>
</ul>



<h2 id="setup-sentry-cho-vue2" class="wp-block-heading">Setup Sentry cho Vue2</h2>



<p>Muốn sử dụng Sentry trước tiên bạn cần có tài khoản, vào <a href="https://sentry.io/signup/ ">https://sentry.io/signup/ </a> để đăng ký tài khoản.</p>



<p>Sau khi đăng ký xong và đăng nhập thành công giao diện sẽ hiển thị như sau: </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="624" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-02-at-18.18.11-1-1024x624.png" alt="" class="wp-image-297" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-02-at-18.18.11-1-1024x624.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-02-at-18.18.11-1-300x183.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-02-at-18.18.11-1-768x468.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-02-at-18.18.11-1-1536x936.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-02-at-18.18.11-1-2048x1248.png 2048w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-02-at-18.18.11-1-380x232.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-02-at-18.18.11-1-800x487.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-02-at-18.18.11-1-1160x707.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-02-at-18.18.11-1.png 2728w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Okay, tiếp theo thì ấn vào nút <strong>Create project</strong>, trong phần tạo đấy, vì mình đang hướng dẫn cho vue nên hãy chọn vuejs, nhập tên project và nhấn nút <strong>Create Project</strong>: </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="701" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-03-at-11.47.06-1024x701.png" alt="" class="wp-image-290" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-03-at-11.47.06-1024x701.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-03-at-11.47.06-300x205.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-03-at-11.47.06-768x526.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-03-at-11.47.06-1536x1051.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-03-at-11.47.06-2048x1402.png 2048w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-03-at-11.47.06-380x260.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-03-at-11.47.06-800x547.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-03-at-11.47.06-1160x794.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-03-at-11.47.06.png 2300w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Vậy là chúng ta đã tạo xong <em>Project</em> trên <strong>Sentry</strong>, bước tiếp theo là lấy <strong>Client Keys (DSN)</strong> để có thể tích hợp vào dự án vuejs, từ <strong>Menu -&gt; Projects -&gt; Chọn Project bạn vừa tạo -&gt; Chọn cài đặt bên góc trên bên phải -&gt; Client Keys (DSN)</strong></p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="653" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-03-at-12.11.51-1024x653.png" alt="" class="wp-image-294" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-03-at-12.11.51-1024x653.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-03-at-12.11.51-300x191.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-03-at-12.11.51-768x490.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-03-at-12.11.51-1536x979.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-03-at-12.11.51-2048x1306.png 2048w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-03-at-12.11.51-380x242.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-03-at-12.11.51-800x510.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-03-at-12.11.51-1160x740.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-03-at-12.11.51.png 2296w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Cài đặt <strong>Sentry</strong> cho vue: <strong><code><mark style="background-color:#e9ecef" class="has-inline-color has-black-color">npm install @sentry/cli</mark></code></strong></p>



<p>Trong <strong>main.js</strong>, khởi tạo <em>Sentry.init</em> với <em>dsn</em> chính là <strong>Client Keys (DSN)</strong> bạn lấy ở bên trên: </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="import Vue from 'vue'
import App from './App.vue'
import * as Sentry from &quot;@sentry/vue&quot;

Vue.config.productionTip = false

Sentry.init({
  Vue,
  dsn: &quot;https://51b9f7xxxxxxxx.ingest.sentry.io/45059xxxxxxxxx&quot;
});

new Vue({
  render: h =&gt; h(App),
}).$mount('#app')
" style="color:#F8F8F2;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> Vue </span><span style="color: #FF79C6">from</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">&#39;</span><span style="color: #F1FA8C">vue</span><span style="color: #E9F284">&#39;</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> App </span><span style="color: #FF79C6">from</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">&#39;</span><span style="color: #F1FA8C">./App.vue</span><span style="color: #E9F284">&#39;</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> Sentry </span><span style="color: #FF79C6">from</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">@sentry/vue</span><span style="color: #E9F284">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">Vue.config.productionTip </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">false</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">Sentry.</span><span style="color: #50FA7B">init</span><span style="color: #F8F8F2">({</span></span>
<span class="line"><span style="color: #F8F8F2">  Vue,</span></span>
<span class="line"><span style="color: #F8F8F2">  dsn</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">https://51b9f7xxxxxxxx.ingest.sentry.io/45059xxxxxxxxx</span><span style="color: #E9F284">&quot;</span></span>
<span class="line"><span style="color: #F8F8F2">});</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6; font-weight: bold">new</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">Vue</span><span style="color: #F8F8F2">({</span></span>
<span class="line"><span style="color: #F8F8F2">  </span><span style="color: #50FA7B">render</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2"> </span><span style="color: #FFB86C; font-style: italic">h</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">=&gt;</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">h</span><span style="color: #F8F8F2">(App),</span></span>
<span class="line"><span style="color: #F8F8F2">}).</span><span style="color: #50FA7B">$mount</span><span style="color: #F8F8F2">(</span><span style="color: #E9F284">&#39;</span><span style="color: #F1FA8C">#app</span><span style="color: #E9F284">&#39;</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span></code></pre></div>



<p>Các bước đã xong, giờ bạn đã có thể test <strong>Sentry</strong> bằng cách tạo thử 1 <em>Exception</em>.</p>



<p>Khi 1 <em>Exception</em> được thực thi, <strong>Sentry</strong> sẽ bắt và trả về cho bạn thông tin xoay quanh <em>Exception</em> đó: </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="545" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-04-at-12.19.38-1024x545.png" alt="" class="wp-image-376" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-04-at-12.19.38-1024x545.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-04-at-12.19.38-300x160.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-04-at-12.19.38-768x409.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-04-at-12.19.38-1536x817.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-04-at-12.19.38-2048x1089.png 2048w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-04-at-12.19.38-380x202.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-04-at-12.19.38-800x426.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-04-at-12.19.38-1160x617.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-04-at-12.19.38-1920x1024.png 1920w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screenshot-2023-10-04-at-12.19.38.png 2722w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 id="tong-ket" class="wp-block-heading">Tổng kết</h2>



<p>Trên đây là thông tin cũng như hướng dẫn cơ bản để có thể tích hợp <strong>Sentry</strong> vào dự án vuejs, còn rất nhiều tính năng hay ho nữa như filter bằng trình duyệt, hỗ trợ source map trong việc gửi thông tin stack trace của exception, &#8230;. bạn hãy tự mình khám phá thêm nhé, mình đã làm được và mình tin bạn cũng sẽ làm được. Hẹn gặp lại ở bài tiếp theo</p>
<p>The post <a href="https://blog.tomosia.com.vn/sentry-io-cho-nguoi-moi-bat-dau-vuejs/">Sentry.io cho người mới bắt đầu (Vuejs)</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.tomosia.com.vn/sentry-io-cho-nguoi-moi-bat-dau-vuejs/feed/</wfw:commentRss>
			<slash:comments>10</slash:comments>
		
		
			</item>
	</channel>
</rss>
