(이 포스트는 https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html를 참고하여 작성하였습니다. )
Vue에서의 Mixins를 이용한 코드 재사용 방법에 대해서 다뤘었지만, Mixins의 사용법 내지는 장점을 위주로 포스팅 했습니다. 이번 포스트는 Vue.js는 아니지만, React.js에서의 Mixin를 해로운 것으로 보는 것이 흥미로워 해당 주제에 대해 다뤄보도록 하겠습니다. 두 프레임워크에서의 Mixin 개념은 비슷하므로, 어떤 점에서 React는 Mixin을 해롭다고 평했는지 알아보도록 하겠습니다.
바쁘신 분들을 위해 단락마다 이렇게 요약된 내용을 적도록 하겠습니다.
믹스인은 해롭다.
"여러 컴포넌트간에 코드를 어떻게 공유합니까?"는 사람들이 React를 배울 때 묻는 첫 번째 질문 중 하나입니다. 우리의 대답은 항상 코드 재사용을 위해 컴포넌트 합성을 사용하는 것이 었습니다. 컴포넌트를 정의하고 다른 여러 컴포넌트에서 사용할 수 있습니다.
React는 함수형 프로그래밍의 영향을 받았지만 객체 지향 라이브러리가 지배하는 분야에 들어왔습니다. 페이스 북의 내부와 외부 엔지니어들이 익숙했던 패턴을 포기하는 것은 어려웠습니다.
이러한 문제 때문에, 초기에 적용하고 공부를 쉽게하기 위해 React에는 탈출구를 넣어뒀죠. mixin
시스템은 그러한 탈출구 중 하나 였고, 목표는 구성과 동일한 문제를 해결하는 방법을 모르는 경우 컴포넌트간에 코드를 재사용 할 수있는 방법을 제공하는 것이 었습니다.
React가 발표 된 지 3년이 지났습니다. 풍경이 바뀌었습니다. 다중 뷰를 가졌던 라이브러리는 이제 React와 유사한 컴포넌트 모델을 채택합니다. 선언적 사용자 인터페이스를 구축하기 위해 상속을 통한 합성 사용은 더 이상 새로운 것이 아닙니다. 우리는 또한 React 컴포넌트 모델에 대해 더 확신하고 있으며 내부적으로나 커뮤니티에서 창의적인 용도로 많이 사용했습니다.
이 글에서는 믹스인에 의해 일반적으로 야기되는 문제를 고려할 것입니다. 그런 다음 동일한 사용 사례에 대해 몇 가지 대안 패턴을 제안 할 것입니다. 우리는 코드베이스의 복잡성에 따라 믹스인보다 대안으로 제시된 패턴이 더 어울린다는 것을 알았습니다.
함수형 프로그래밍에 익숙한 사람들이 모여 객체 지향 라이브러리가 지배하는 자바스크립트에 들어와 만든 것이 리액트이며, 객체지향에 대한 탈출구로 믹스인을 많이 썼지만 더 좋은 대안이 있다는 것을 알았다.
왜 믹스인이 해롭다는거지
Facebook에서 점점 React를 많이 사용하기 시작했습니다. 사람들은 이를 통해 어떻게 React를 써야할지 가늠하게 되었죠. 선언적 렌더링과 하향식 데이터 흐름 덕분에 많은 팀이 버그를 수정하면서 새로운 기능을 제공 할 수있었습니다.
그러나 React팀은 대부분의 사람이 만지는 것을 두려워하는 컴포넌트들을 발견합니다. 이러한 컴포넌트들은 깨지기 쉽고, 새로운 개발자를 혼란스럽게 만드는 컴포넌트였죠. 이러한 혼란은 대부분 믹스인에서 발견되었습니다. 당시 필자는 페이스북에서 일하고 있지 않았지만, 믹스인이 결국 끔찍하다는 것으로 결론지었죠.
물론 믹스인이라는 기능 자체가 나쁘다는 것이 아닙니다. 당연하게도 필요한 곳에 이러한 기능을 성공적으로 사용하는 분들도 있습니다. 페이스북에서는 mixin과 상당히 유사한 Hack을 효과적으로 사용하고 있습니다. 그럼에도 불구하고 React의 코드에서 믹스인은 불필요하고 문제가 있다고 생각합니다. 그 이유는 다음과 같습니다.
대부분의 혼란을 야기하는 코드베이스에는 믹스인이 사용되고 있었고, 결국 믹스인은 끔찍하다는 것으로 결론지었다. (물론 잘 쓰면 된다! 그게 어렵다!)
믹스인은 암시적 의존성을 만듭니다.
때때로 컴포넌트는 mixin에 정의 된 특정 메서드에 의존합니다. 컴포넌트가 믹스인의 getClassName()
나 renderHeader()
와 같은 메서드를 호출할 수 있습니다. JavaScript는 동적 인 언어이므로 이러한 종속성을 시행하거나 문서화하기가 어렵습니다.
믹스인은 누구든 수정할 수 있고, 다른 컴포넌트에서 사용할 수도 있습니다. 하지만 해당 컴포넌트가 믹스인의 메소드를 쓰려면 믹스인에는 정의되지 않은 이름으로 작성을 해야하죠. 이러한 암시적 종속성으로 인해 새로운 팀 구성원이 코드를 다루기가 어려워집니다. 컴포넌트의 render()
메소드는, 그 클래스로 정의되어 있지 않은 메소드를 참조 할 가능성이 있습니다. 아마도 믹스 인 중 하나에 정의되어있을 것입니다. 이를 고치거나 참고하려면 연관된 모든 mixin을 열어서 스크롤 해봐야하는 것이죠. 더 나쁜 것은 믹스인이 자신의 믹스인을 지정할 수 있기 때문에 더욱더 복잡해질 가능성이 있습니다.
종종 믹스인은 다른 믹스인에 의존하게되고, 그 중 하나를 제거하면 다른 믹스인을 깨뜨릴 수 있습니다. 이러한 상황에서는 데이터가 믹스인 안팎으로 어떻게 흐르고 있는지, 그리고 의존성 그래프가 어떻게 생겼는지 알기가 매우 까다 롭습니다. 컴포넌트와 달리 믹스인은 계층을 형성하지 않습니다. 즉, 계층이 아닌 혼합을 이루어 동일한 네임스페이스에서 작동합니다.
믹스인은 한 컴포넌트에 있는 파일이 아니기 때문에 종속성이 생기고, 문서화하기 어렵다. 왜냐하면 해당 코드를 찾으려는 개발자는 혼합된 믹스인을 다 찾아봐야 하기 때문이다.
믹스인은 이름 충돌을 야기합니다.
두 가지 특정 mixin을 함께 사용할 수 있다는 보장은 없습니다. 예를 들어, FluxListenerMixin
믹스인 파일의 handleChange()
메소드와 WindowSizeMixin
믹스인의 handleChange()
mixin 코드를 제어한다면 큰 문제는 아니겠죠. 충돌이 발생하면 mixins 중 하나에서 해당 메서드의 이름을 바꿀 수 있습니다. 그러나 일부 컴포넌트나 다른 mixin이 이미 이 메서드를 직접 호출 할 수 있기 때문에 까다롭습니다. 이러한 호출도 모두 찾아내서 수정해야합니다.
타 패키지의 믹스인과 이름이 충돌하면 메서드의 이름을 바꿀 수 없습니다. 대신 충돌을 피하기 위해 컴포넌트에 어색한 메서드 이름을 사용해야합니다.
mixin을 작성한 개발자에게는 상황이 더 좋지 않습니다. mixin에 새로운 메소드를 추가하는 것조차도, 직접 또는 다른 mixin을 통해 동일한 이름을 가진 메소드가 이미 일부 컴포넌트에 존재할 수 있으므로 항상 잠재적인 위험 사항입니다. 작성된 mixins는 제거하거나 변경하기가 어렵습니다.
타 패키지나, 믹스인 심지어 믹스인이 적용될 컴포넌트까지 메소드명이 충돌될 수 있다!
믹스인은 복잡성을 눈덩이처럼 불어나게합니다.
믹스인이 단순 해지기 시작했다하더라도, 시간이 지남에 따라 복잡해지는 경향이 있습니다. 아래 예제는 코드베이스에서 실제로 본 시나리오를 기반으로합니다.
우리의 기본적인 니즈는 컴포넌트는 마우스의 hover에 따른 액션을 하는 코드를 만드는 것 입니다. 자 어떻게 코드베이스가 복잡해지는지 다음의 순서를 따라가봅시다.
로직의 재사용성을 지키기 위해,
handleMouseEnter()
,handleMouseLeave()
,isHovering()
을HoverMixin
파일에 넣고 필요한 컴포넌트에서 사용합니다.tooltip을 실행하기 위해 이를 사용한다고 합시다. 그들은
HoverMixin
의 로직을 중복되게 사용하고 싶지 않아TooltipMixin
이라는 믹스인을HoverMixin
을 사용하여 만듭니다. 이 믹스인은isHovering()
를 확장한componentDidUpdate()
라는 메소드를 정의합니다.몇 달 후 누군가가 툴팁 방향을 설정 가능하게 만들고 싶어합니다. 코드 중복을 피하기 위한 일환으로 그들은 새로운 메소드인
getTooltipOptions()
라는 메소드를TooltipMixin
에 추가합니다.툴팁의 popover 액션은
HoverMixin
을 사용하여 보여줬습니다. 그러나 popover는 툴팁마다 hover 속도를 다르게 보여줘야 된다고 합니다. 이것을 해결하기 위해 누군가가 또TooltipMixin
에getHoverOptions()
라는 메소드를 추가하게됩니다.
그렇게 이 두 믹스인은 단단하게 커플링되어갑니다. (정확히 이해를 못해도 이리저리 코드의 종속성이 생긴다는 것만 잘 이해하시면 됩니다.)
새로운 요구사항이 있을 때마다 믹스인을 이해하기가 더 어렵습니다. 동일한 mixin을 사용하는 컴포넌트는 점차 결합됩니다. 모든 새로운 기능은 해당 믹스인을 사용하는 컴포넌트에 추가됩니다. 점차적으로 캡슐화 경계가 침식되고, 기존 믹스인을 변경하거나 제거하기가 어렵기 때문에 믹스인은 점점 더 추상화 될 뿐입니다.
이는 React 이전에 앱을 제작할 때 직면 한 것과 동일한 문제입니다. 선언적 렌더링, 하향식 데이터 흐름 및 캡슐화 된 컴포넌트에 의해 해결된 것으로 나타났습니다. 페이스북에서, 우리는 mixins 대신 사용할 수 있는 대안 패턴으로 대체하도록 마이그레이션 했습니다. 아래에 대안 패턴을 준비해뒀습니다.
믹스인의 기능을 확장하면서 점점 다른 믹스인이 생겨나게 되고, 결과적으로 믹스인들끼리는 강한 종속성이 생겨버린다. 이를 위한 대안 패턴을 준비해뒀다!
믹스인으로부터 벗어나기
mixins가 기술적으로 더 이상 사용되지 않는다는 것을 분명히합시다. 믹스인은 우리를 위해 잘 작동하지 않는다고 말했기 때문에 앞으로는 사용하지 않을 것을 권장합니다.
아래의 모든 섹션은 Facebook 코드베이스에서 발견 된 mixin 사용 패턴에 해당합니다. 각각에 대해 우리는 믹스인보다 더 잘 작동한다고 생각하는 해결책을 설명합니다. 예제는 ES5로 작성되었지만 믹스인이 필요 없으면 원하는 경우 ES6 클래스로 전환 할 수 있습니다.
퍼포먼스 최적화
가장 일반적으로 사용되는 믹스인 중 하나입니다 PureRenderMixin
. 소품과 상태가 이전 소품 및 상태와 동등하게 작을 때 불필요한 재 렌더링 을 방지하기 위해 컴포넌트에서이를 사용할 수도 있습니다 .
var PureRenderMixin = require('react-addons-pure-render-mixin');
var Button = React.createClass({
mixins: [PureRenderMixin],
// ...
});
해결책
믹스 인을 사용하지 않고 똑같이 표현하려면 shallowCompare
함수를 직접 대신 사용할 수 있습니다 .
var shallowCompare = require('react-addons-shallow-compare');
var Button = React.createClass({
shouldComponentUpdate: function(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
},
// ...
});
shouldComponentUpdate
알고리즘이 다른 함수를 구현하는 사용자 정의 믹스인을 사용하는 경우 모듈에서 해당 함수 하나만 내보내고 컴포넌트에서 직접 호출하는 것이 좋습니다.
더 많은 타이핑이 성가신 일이라는 것을 알고 있습니다. 가장 일반적인 경우, 다음 릴리스에서 호출 되는 새로운 기본 클래스를(React.PureComponent) 도입할 계획입니다.
'react-addons-pure-render-mixin' 과 같은 주제에 대해서는 잘 모르겠지만, 필요하다면 믹스인 된 모듈 자체를 가져오는 것이 아닌 함수 하나만 export, import 하는 것이 좋다고 말하고 있습니다.
구독(Subscription)과 부작용
두 번째로 흔한 유형의 믹스인은 React 컴포넌트를 써드파티 데이터 소스에 등록하는 mixins입니다. 이 데이터 소스가 Flux Store이든, Rx Observable이든 관계없이 패턴은 매우 유사합니다. 다음 메소드에서 구독이 생성되고 - componentDidMount
, 파기되며 - componentWillUnmount
변경 핸들러는 this.setState()
를 호출합니다.
var SubscriptionMixin = {
getInitialState: function() {
return {
comments: DataSource.getComments()
};
},
componentDidMount: function() {
DataSource.addChangeListener(this.handleChange);
},
componentWillUnmount: function() {
DataSource.removeChangeListener(this.handleChange);
},
handleChange: function() {
this.setState({
comments: DataSource.getComments()
});
}
};
var CommentList = React.createClass({
mixins: [SubscriptionMixin],
render: function() {
// Reading comments from state managed by mixin.
var comments = this.state.comments;
return (
<div>
{comments.map(function(comment) {
return <Comment comment={comment} key={comment.id} />
})}
</div>
)
}
});
module.exports = CommentList;
해결책
만약 이 데이터 소스에 하나의 컴포넌트만 구독한다면, 구독 로직을 컴포넌트 안에 넣는 것이 좋습니다. 하나 뿐인데도 성급한 추상화를 하진 마세요.
여러 컴포넌트가 이 믹스인을 사용하여 데이터 소스에 가입하는 경우 반복을 피하는 좋은 방법은 고차원 컴포넌트 라는 패턴을 사용하는 것입니다. 리액트 팀이 강제로 밀어붙인다는 소리가 나올 수 있으므로 이 패턴이 자연스럽게 컴포넌트 모델에서 어떻게 나오는지 자세히 살펴볼 것입니다.
고차원 컴포넌트
React를 잠시 잊어 버립시다. 숫자를 더하고 곱하는 다음 두 가지 기능을 고려하여 결과를 로깅합니다.
function addAndLog(x, y) {
var result = x + y;
console.log('result:', result);
return result;
}
function multiplyAndLog(x, y) {
var result = x * y;
console.log('result:', result);
return result;
}
이 두 함수는 별로 유용하지 않지만 나중에 컴포넌트에 적용 할 수 있는 패턴을 보여줍니다.
함수 시그니쳐를 변경하지 않고 이러한 함수에서 로깅 로직을 추출하려고 한다고 가정 해 봅시다. 우리는 어떻게 이 일을 할 수 있습니까? 우아한 해결책은 고차 함수 , 즉 함수를 인수로 취하여 함수를 반환하는 함수를 작성하는 것입니다.
function withLogging(wrappedFunction) {
// Return a function with the same API...
return function(x, y) {
// ... that calls the original function
var result = wrappedFunction(x, y);
// ... but also logs its result!
console.log('result:', result);
return result;
};
}
withLogging
고차 함수는 우리가 add와 multiply 함수를 로깅 구문 없이 작성할 수 있게 합니다. 고차 함수로 감싼 후 addAndLog
나 multiplyAndLog
처럼 정확히 같은 함수 시그니처를 유지할 수 있게 합니다.
function add(x, y) {
return x + y;
}
function multiply(x, y) {
return x * y;
}
function withLogging(wrappedFunction) {
return function(x, y) {
var result = wrappedFunction(x, y);
console.log('result:', result);
return result;
};
}
// Equivalent to writing addAndLog by hand:
var addAndLog = withLogging(add);
// Equivalent to writing multiplyAndLog by hand:
var multiplyAndLog = withLogging(multiply);
고차원 컴포넌트는 매우 유사한 패턴이지만 React의 컴포넌트에 적용됩니다. mixins에서 이 변환을 두 단계로 적용 할 것입니다.
첫 번째 단계로서, 우리는 CommentList
컴포넌트를 두 가지 측면, 즉 자녀와 부모로 컴포넌트를 분리합니다 . 자식은 단지 코멘트를 렌더링 하는 것만 신경씁니다. 부모는 구독을 셋업하고 최신 데이터를 자식에게 props로 전달합니다.
// Child Component
// 부모로부터 받은 props 데이터로 comment만 렌더링한다.
var CommentList = React.createClass({
render: function() {
// Note: now reading from props rather than state.
var comments = this.props.comments;
return (
<div>
{comments.map(function(comment) {
return <Comment comment={comment} key={comment.id} />
})}
</div>
)
}
});
// Parent Component
// 데이터 소스를 구독하고 <CommnetList />(Child Component)를 렌더링한다.
var CommentListWithSubscription = React.createClass({
getInitialState: function() {
return {
comments: DataSource.getComments()
};
},
componentDidMount: function() {
DataSource.addChangeListener(this.handleChange);
},
componentWillUnmount: function() {
DataSource.removeChangeListener(this.handleChange);
},
handleChange: function() {
this.setState({
comments: DataSource.getComments()
});
},
render: function() {
// We pass the current state as props to CommentList.
return <CommentList comments={this.state.comments} />;
}
});
module.exports = CommentListWithSubscription;
할 일이 하나 남았습니다.
withLogging()
함수에서 우리는 함수를 취하고 랩핑된 새로운 함수를 리턴했던 것을 기억해봅시다. 우리는 React 컴포넌트에 비슷한 패턴을 적용할 수 있습니다.
자 withSubscription(WrappedComponent)
라는 함수를 만들어 봅시다. 이 함수의 인자는 React 컴포넌트입니다. 이 예에서는 CommentList
를 WrappedComponent
로 넘길 것이지만, 다른 컴포넌트들도 이 함수를 이용해 구독 기능을 래핑할 수 있습니다.
이 함수는 다른 컴포넌트를 반환합니다. 리턴된 컴포넌트는 구독을 관리하고 <WrappedComponent />
를 현재 데이터와 함께 렌더링 합니다. 우리는이 패턴을 고차원 컴포넌트라고 부릅니다.
function withSubscription(WrappedComponent) {
return React.createClass({
getInitialState: function() {
return {
comments: DataSource.getComments()
};
},
componentDidMount: function() {.
DataSource.addChangeListener(this.handleChange);
},
componentWillUnmount: function() {
DataSource.removeChangeListener(this.handleChange);
},
handleChange: function() {
this.setState({
comments: DataSource.getComments()
});
},
render: function() {
// 신선한 데이터와 함께 렌더링합니다!
return <WrappedComponent comments={this.state.comments} />;
}
});
}
우리는 이제 CommentList
에 withSubscription
기능을 적용한 CommentListWithSubscription
를 선언할 수 있습니다.
var CommentList = React.createClass({
render: function() {
var comments = this.props.comments;
return (
<div>
{comments.map(function(comment) {
return <Comment comment={comment} key={comment.id} />
})}
</div>
)
}
});
// withSubscription() returns a new component that
// is subscribed to the data source and renders
// <CommentList /> with up-to-date data.
var CommentListWithSubscription = withSubscription(CommentList);
// The rest of the app is interested in the subscribed component
// so we export it instead of the original unwrapped CommentList.
module.exports = CommentListWithSubscription;
솔루션, 재검토
고차원 컴포넌트를 더 잘 이해 했으므로 믹스인을 포함하지 않는 완벽한 솔루션을 다시 한 번 살펴 보겠습니다. 인라인 주석으로 주석 처리 된 몇 가지 사소한 변경 사항이 있습니다.
function withSubscription(WrappedComponent) {
return React.createClass({
getInitialState: function() {
return {
comments: DataSource.getComments()
};
},
componentDidMount: function() {
DataSource.addChangeListener(this.handleChange);
},
componentWillUnmount: function() {
DataSource.removeChangeListener(this.handleChange);
},
handleChange: function() {
this.setState({
comments: DataSource.getComments()
});
},
render: function() {
// Use JSX spread syntax to pass all props and state down automatically.
return <WrappedComponent {this.props} {this.state} />;
}
});
}
// Optional change: convert CommentList to a function component
// because it doesn't use lifecycle methods or state.
function CommentList(props) {
var comments = props.comments;
return (
<div>
{comments.map(function(comment) {
return <Comment comment={comment} key={comment.id} />
})}
</div>
)
}
// Instead of declaring CommentListWithSubscription,
// we export the wrapped component right away.
module.exports = withSubscription(CommentList);
고차원 컴포넌트는 강력한 패턴입니다. 자신의 행동을 추가로 사용자 정의하려는 경우 추가 인수를 전달할 수 있습니다. 결국, 그들은 심지어 React의 특징이 아닙니다. 컴포넌트를 받고 그것을 감싸는 컴포넌트를 반환하는 함수 일뿐입니다.
다른 솔루션과 마찬가지로 고차 컴포넌트에는 자체 함정이 있습니다. 예를들어 만약 당신이 무거운 ref를 사용한다면, 당시은 고차 컴포넌트에 랩핑된 어떤 것이 ref를 랩핑 컴포넌트를 바꿀 것입니다. 실제적으로 우리는 컴포넌트 커뮤니케이션을 위해 refs를 사용하는 것을 추천하지 않기 때문에 큰 이슈는 아닙니다. 미래에 우리는 ref forwarding을 추가하여 해결할 예정입니다.
어떤 기능이 필요한 여러 컴포넌트가 있다고 해서 그 기능을 mixin으로 묶는 것이 아니라, 고차 컴포넌트(HOC: Higher Order Component)를 사용하여 랩핑하라고 합니다.
렌더링 로직
다음으로 코드베이스에서 발견 된 믹스인에 대한 가장 일반적인 사용 사례는 컴포넌트간에 렌더링 로직을 공유하는 것입니다.
다음은 이 패턴의 전형적인 예입니다.
var RowMixin = {
// Called by components from render()
renderHeader: function() {
return (
<div className='row-header'>
<h1>
{this.getHeaderText() /* Defined by components */}
</h1>
</div>
);
}
};
var UserRow = React.createClass({
mixins: [RowMixin],
// Called by RowMixin.renderHeader()
getHeaderText: function() {
return this.props.user.fullName;
},
render: function() {
return (
<div>
{this.renderHeader() /* Defined by RowMixin */}
<h2>{this.props.user.biography}</h2>
</div>
)
}
});
여러 컴포넌트가 RowMixin
을 렌더링을 위해 공유 할 수 있으며 각 컴포넌트는 getHeaderText()
를 정의해야합니다 .
해결책
믹스인 안의 렌더링 로직을 봤다면, 이제 컴포넌트를 추출할 때입니다!
RowMixin
대신에 우리는 <RowHeader>
컴포넌트를 정의 할 것 입니다. 또한 getHeaderText()
메소드를 React의 탑다운 방식으로 컨벤션을 바꿀 것입니다. (React: props 전달)
마지막으로, 현재 이들 컴포넌트 중 어느 것도 라이프 사이클 메소드나 상태를 필요로하지 않으므로 이를 간단한 함수로 선언 할 수 있습니다.
function RowHeader(props) {
return (
<div className='row-header'>
<h1>{props.text}</h1>
</div>
);
}
function UserRow(props) {
return (
<div>
<RowHeader text={props.user.fullName} />
<h2>{props.user.biography}</h2>
</div>
);
}
Props는 컴포넌트간 종속성을 분명하게 유지시켜줍니다. 쉽게 교체하고 실행하도록 Flow와 TypeScript를 참고하세요.
노트 :
컴포넌트를 함수로 정의 할 필요가 없습니다. 라이프 사이클 메소드와 상태를 사용하는 데에도 아무런 문제가 없습니다. 이들은 React 일급 클래스 기능입니다. 이 예제에서는 함수 컴포넌트를 사용하기 때문에 읽기 쉽고 이러한 추가 기능은 필요하지 않지만 클래스는 정상적으로 작동합니다.
믹스인보다는 부모에서 자식으로 props를 전달하는 방법을 사용합니다.
유틸리티 메소드
믹스인은 때때로 구성 요소 들간의 유틸리티 기능을 공유하기 위해서만 사용되기도합니다 :
var ColorMixin = {
getLuminance(color) {
var c = parseInt(color, 16);
var r = (c & 0xFF0000) >> 16;
var g = (c & 0x00FF00) >> 8;
var b = (c & 0x0000FF);
return (0.299 * r + 0.587 * g + 0.114 * b);
}
};
var Button = React.createClass({
mixins: [ColorMixin],
render: function() {
var theme = this.getLuminance(this.props.color) > 160 ? 'dark' : 'light';
return (
<div className={theme}>
{this.props.children}
</div>
)
}
});
해결책
유틸리티 함수를 일반 자바 스크립트 모듈에 넣고 가져옵니다. 쉽게 테스트하거나, 사용하기 위해 컴포넌트 외부에서 사용하는 것이 더 쉽습니다.
var getLuminance = require('../utils/getLuminance');
var Button = React.createClass({
render: function() {
var theme = getLuminance(this.props.color) > 160 ? 'dark' : 'light';
return (
<div className={theme}>
{this.props.children}
</div>
)
}
});
단순히 유틸성의 함수를 사용한다면 객체에 믹스인 시키는 것이 아니라 간단하게 export, import를 사용하라고 합니다.
기타 사용 사례
때때로 사람들은 믹스인을 사용하여 일부 컴포넌트의 라이프 사이클 메소드에 선택적으로 로깅을 추가합니다. 앞으로는 컴포넌트를 건드리지 않고 비슷한 기능을 구현할 수있는 공식 DevTools API 를 제공 할 계획 입니다. 그러나 여전히 진행중인 작업입니다. 디버깅을 위해 mixin을 로깅하는 데 크게 의존한다면, 조금 더 오래 믹스인을 계속 사용하고 싶을 것입니다.
컴포넌트, 고차원 컴포넌트 또는 유틸리티 모듈로 무언가를 성취 할 수 없다면 React가 이를 즉시 제공해야 함을 의미 할 수 있습니다. 믹스인에 대한 사용 사례를 알려주는 문제 제기를 하십시오. 대안을 고려하거나 기능 요청을 구현하는 데 도움을 드릴 것입니다.
우리는 위의 대안이 대다수의 경우에 더 좋다고 믿고 있으며, mixins를 사용하지 않고 React apps를 쓰도록 권유합니다.
'Frontend > Vue.js' 카테고리의 다른 글
[Vue.js] Vue Mixins란? (2) | 2018.12.29 |
---|