#
Tản mạn
styled-components là thư viện mình rất thích khi viết css cho react, sau vài ngày gần đây mình tìm hiểu về source của nó thì mình đã quyết định viết lại nó để hiểu sâu nhất có thể, mình cảm thấy học được rất repo này nên mình đã viết ra bài nên đã chia sẻ cho ae những thứ mình đã học được từ nó, để hiểu được những đoạn code trong repo của mình thì mình sẽ nói 2 khái niệm khá mới với mình và bắt buộc phải hiểu, nếu ae đã biết rồi thì có thể bỏ qua ^^
Stylesheet
Nếu ae nào biết đến stylesheet trong javascript rồi thì có thể bỏ qua đoạn này
Trong styled-components thì nó đc dùng để lưu các style, mỗi thẻ tag style thì có 1 sheet, mỗi sheet có rất nhiều rule , 1 rule tương ứng với className và style kèm theo, vd ở dưới minh hoạ cho việc thêm 1 rule vào sheet
1 2 3 4 5 | // get tất cả các các sheet trong app const sheet = document.styleSheets; //chọn 1 rule trong sheet và add css vào rule sheet[1].insertRule ('.classname {width : 100%;height : 100%;}', indexStyle) |
Link chi tiết về nó : https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet
##Tagged templates
Tagged templates mình nói đến ở đây chính là cú pháp hay dùng ở styled-components
1 2 3 4 5 6 7 8 | <span class="token comment">// function này chỉ return 1 array chứ tất cả các đối số của function này</span> <span class="token keyword">const</span> <span class="token function-variable function">templateFunc</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token operator">...</span>args <span class="token punctuation">)</span> <span class="token operator">=></span> args templateFunc<span class="token template-string"><span class="token string">` width : 100%; height : 50%; background : </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>props <span class="token operator">=></span> props<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">; `</span></span> |
Kết qủa chúng ta lấy ra đc các tham số như thế này
Link chi tiết về nó : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
##Bắt tay vào viết thư viện
Mình sẽ viết class ComponentStyle để handle việc khởi tạo stylesheet và insert css cho component
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <span class="token keyword">class</span> <span class="token class-name">ComponentStyle</span> <span class="token punctuation">{</span> sheet <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// ngay khi class khởi tạo thì mình sẽ tạo thẻ style và lưu sheet của style đó vào biến sheet trong class</span> <span class="token keyword">const</span> styleDom <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">makeStyleTag</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// lấy sheet và save vào biến this.sheet </span> <span class="token keyword">this</span><span class="token punctuation">.</span>sheet <span class="token operator">=</span> styleDom<span class="token punctuation">.</span>sheet<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function-variable function">makeStyleTag</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// function này sẽ tạo style và add thẻ style đó vào thẻ head</span> <span class="token keyword">const</span> style <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">"style"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> style<span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span><span class="token string">"data-style-duc-version"</span><span class="token punctuation">,</span> <span class="token string">"1.0.0"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> document<span class="token punctuation">.</span>head<span class="token punctuation">.</span><span class="token function">insertBefore</span><span class="token punctuation">(</span>style<span class="token punctuation">,</span> document<span class="token punctuation">.</span>head<span class="token punctuation">.</span>childNodes<span class="token punctuation">[</span>document<span class="token punctuation">.</span>head<span class="token punctuation">.</span>childNodes<span class="token punctuation">.</span>length<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> style<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token function">insertBefore</span><span class="token punctuation">(</span>css<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// function sẽ tạo ra tên các class một cách random cho các component và return về tên class đó</span> <span class="token keyword">const</span> className <span class="token operator">=</span> <span class="token function">uuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> newName <span class="token operator">=</span> <span class="token string">"style-duc-"</span> <span class="token operator">+</span> className<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>sheet<span class="token punctuation">.</span><span class="token function">insertRule</span><span class="token punctuation">(</span> <span class="token template-string"><span class="token string">`.</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>newName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">{</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>css<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">}`</span></span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>sheet<span class="token punctuation">.</span>cssRules<span class="token punctuation">.</span>length <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> newName<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Phần code trong file core.js chắc là cũng là phần lằng nhằng nhất, mình nghĩ đê ae hiểu nhất về đoạn code của mình thì nên clone repo này của mình rồi chạy nó rồi vào đây đọc thì sẽ dễ hiểu hơn rất nhiều
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | <span class="token comment">// function này sẽ trực tiếp tạo ra Element React </span> <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">createStyledComponent</span><span class="token punctuation">(</span>target<span class="token punctuation">,</span> css<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> WrappedStyledComponent <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// khởi taọ class ComponentStyle</span> <span class="token keyword">const</span> componentStyle <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ComponentStyle</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// lưu nó vào trong biến WrappedStyledComponent, để tí nữa ở func useStyledComponentImpl sẽ lôi ra dùng</span> WrappedStyledComponent<span class="token punctuation">.</span>componentStyle <span class="token operator">=</span> componentStyle<span class="token punctuation">;</span> WrappedStyledComponent<span class="token punctuation">.</span>target <span class="token operator">=</span> target<span class="token punctuation">;</span> <span class="token comment">// eslint-disable-next-line react-hooks/rules-of-hooks</span> <span class="token keyword">const</span> <span class="token function-variable function">forwardRef</span> <span class="token operator">=</span> <span class="token punctuation">(</span>props<span class="token punctuation">,</span> ref<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">useStyledComponentImpl</span><span class="token punctuation">(</span>WrappedStyledComponent<span class="token punctuation">,</span> props<span class="token punctuation">,</span> ref<span class="token punctuation">,</span> css<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> Element <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">forwardRef</span><span class="token punctuation">(</span>forwardRef<span class="token punctuation">)</span><span class="token punctuation">;</span> Element<span class="token punctuation">.</span><span class="token function-variable function">toString</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token template-string"><span class="token string">`.</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>WrappedStyledComponent<span class="token punctuation">.</span>newClassToString<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> Element<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> <span class="token function-variable function">renderCss</span> <span class="token operator">=</span> <span class="token punctuation">(</span>cssRaw<span class="token punctuation">,</span> propsElement<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// function này sẽ handle việc map mảng cssRaw thành chuỗi css, mỗi một element trong mảng cssRaw thì có thể function hoặc có thể là string</span> <span class="token keyword">let</span> css <span class="token operator">=</span> <span class="token string">""</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> elementCss <span class="token keyword">of</span> cssRaw<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> elementCss <span class="token operator">===</span> <span class="token string">'function'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token function">elementCss</span><span class="token punctuation">(</span>propsElement<span class="token punctuation">)</span><span class="token punctuation">;</span> css <span class="token operator">+=</span> result<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> elementCss <span class="token operator">===</span> <span class="token string">"string"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> css <span class="token operator">+=</span> elementCss<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> css<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// CORE FUNCTION </span> <span class="token comment">// function này mình sẽ handle việc tạo ra react component, merge props , truyền ref </span> <span class="token keyword">const</span> <span class="token function-variable function">useStyledComponentImpl</span> <span class="token operator">=</span> <span class="token punctuation">(</span>WrappedStyledComponent<span class="token punctuation">,</span> props<span class="token punctuation">,</span> ref<span class="token punctuation">,</span> css<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// support cho vấn đề ThemeProvider trong styled-componets</span> <span class="token keyword">const</span> theme <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useContext</span><span class="token punctuation">(</span>ThemeContext<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> newProps <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token operator">...</span>props<span class="token punctuation">,</span> <span class="token operator">...</span><span class="token punctuation">{</span> theme<span class="token punctuation">,</span> ref <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> newCss <span class="token operator">=</span> <span class="token function">renderCss</span><span class="token punctuation">(</span>css<span class="token punctuation">,</span> newProps<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// insert css và get đc className mới</span> <span class="token keyword">const</span> className <span class="token operator">=</span> WrappedStyledComponent<span class="token punctuation">.</span>componentStyle<span class="token punctuation">.</span><span class="token function">insertBefore</span><span class="token punctuation">(</span>newCss<span class="token punctuation">)</span><span class="token punctuation">;</span> WrappedStyledComponent<span class="token punctuation">.</span>newClassToString <span class="token operator">=</span> className<span class="token punctuation">;</span> <span class="token comment">// nối tên các className</span> newProps<span class="token punctuation">.</span>className <span class="token operator">=</span> <span class="token punctuation">[</span>props<span class="token punctuation">.</span>className<span class="token punctuation">,</span> className<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">" "</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// cuối cùng là tạo react element</span> <span class="token keyword">return</span> React<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span>WrappedStyledComponent<span class="token punctuation">.</span>target<span class="token punctuation">,</span> newProps<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> |
Cuối cùng là code trong file index.js, chính là file mà ae sao này sẽ import nó mỗi khi dùng styled-components
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <span class="token comment">// domElements là một mảng chứa các thẻ trong html , domElements = ['a,', 'div', 'section','svg', ....], </span> <span class="token comment">//kỹ hơn thì các bạn vào repo của mình xem, vì nó dài nên mình ko trích vào đây</span> <span class="token keyword">import</span> domElements <span class="token keyword">from</span> <span class="token string">'./utils/domElements'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> createStyledComponent <span class="token keyword">from</span> <span class="token string">'./core'</span> <span class="token keyword">import</span> css <span class="token keyword">from</span> <span class="token string">'./utils/css'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span>createContext<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react'</span> <span class="token keyword">export</span> <span class="token keyword">const</span> ThemeContext <span class="token operator">=</span> <span class="token function">createContext</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token keyword">export</span> <span class="token keyword">const</span> ThemeProvider <span class="token operator">=</span> ThemeContext<span class="token punctuation">.</span>Provider <span class="token keyword">const</span> <span class="token function-variable function">styled</span> <span class="token operator">=</span> <span class="token punctuation">(</span>tag<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token operator">...</span>args<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">createStyledComponent</span><span class="token punctuation">(</span>tag<span class="token punctuation">,</span> <span class="token function">css</span><span class="token punctuation">(</span><span class="token operator">...</span>args<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> domElements<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>domElement <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// đoạn này mình gán thể này để tí nữa mọi người có thể dùng như thế này : styled.div` ... `</span> styled<span class="token punctuation">[</span>domElement<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">styled</span><span class="token punctuation">(</span>domElement<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">default</span> styled |
Và okay rồi, ae có thể import styled-componets fake của mình vào để xài như một thư viện thật =))))
Mọi có thể vào thẳng link sandbox để xem và code demo luôn
https://codesandbox.io/embed/github/ducga1998/rewrite-styled-components/tree/master/?fontsize=14&hidenavigation=1&theme=dark
Kết
Hi vọng mọi người sẽ thấy thú vị qua bài viết của mình, xin cảm ơn ae đã đọc đến tận đây ạ : ))
Repo : https://github.com/ducga1998/rewrite-styled-components